console/deployment/utils/reverse-proxy.ts

269 lines
7.1 KiB
TypeScript
Raw Normal View History

2022-05-18 07:26:57 +00:00
import * as k8s from '@pulumi/kubernetes';
import { Output } from '@pulumi/pulumi';
import { ContourValues } from './contour.types';
import { helmChart } from './helm';
2022-05-18 07:26:57 +00:00
// prettier-ignore
export const CONTOUR_CHART = helmChart('https://charts.bitnami.com/bitnami', 'contour', '17.0.0');
2022-05-18 07:26:57 +00:00
export class Proxy {
private lbService: Output<k8s.core.v1.Service> | null = null;
constructor(
private tlsSecretName: string,
private staticIp?: { address?: string; aksReservedIpResourceGroup?: string },
) {}
2022-05-18 07:26:57 +00:00
registerService(
dns: { record: string; apex?: boolean },
routes: {
name: string;
path: string;
service: k8s.core.v1.Service;
timeoutInSeconds?: number;
retriable?: boolean;
2022-05-18 07:26:57 +00:00
customRewrite?: string;
virtualHost?: Output<string>;
httpsUpstream?: boolean;
withWwwDomain?: boolean;
}[],
2022-05-18 07:26:57 +00:00
) {
const cert = new k8s.apiextensions.CustomResource(`cert-${dns.record}`, {
apiVersion: 'cert-manager.io/v1',
kind: 'Certificate',
metadata: {
name: dns.record,
},
spec: {
commonName: dns.record,
dnsNames: [dns.record],
issuerRef: {
name: this.tlsSecretName,
kind: 'ClusterIssuer',
},
secretName: dns.record,
},
});
new k8s.apiextensions.CustomResource(
`httpproxy-${dns.record}`,
{
apiVersion: 'projectcontour.io/v1',
kind: 'HTTPProxy',
metadata: {
name: `ingress-${dns.record}`,
},
spec: {
virtualhost: {
fqdn: dns.record,
tls: {
secretName: dns.record,
},
corsPolicy: {
allowOrigin: ['https://app.graphql-hive.com', 'https://graphql-hive.com'],
2022-05-18 07:26:57 +00:00
allowMethods: ['GET', 'POST', 'OPTIONS'],
allowHeaders: ['*'],
exposeHeaders: ['*'],
},
},
routes: routes.map(route => ({
2022-05-18 07:26:57 +00:00
conditions: [
{
prefix: route.path,
},
],
services: [
{
name: route.service.metadata.name,
port: route.service.spec.ports[0].port,
},
],
...(route.path === '/'
? {}
: {
pathRewritePolicy: {
replacePrefix: [
{
replacement: route.customRewrite || '/',
},
],
},
...(route.timeoutInSeconds
? {
timeoutPolicy: {
response: `${route.timeoutInSeconds}s`,
},
}
: {}),
...(route.retriable
? {
retryPolicy: {
count: 2,
retryOn: ['reset', 'retriable-status-codes'],
retriableStatusCodes: [503],
},
}
: {}),
2022-05-18 07:26:57 +00:00
}),
})),
},
},
{
dependsOn: [cert, this.lbService!],
},
2022-05-18 07:26:57 +00:00
);
return this;
}
2024-03-26 08:18:00 +00:00
deployProxy(options: {
envoy: {
replicas?: number;
memory?: string;
cpu?: string;
};
}) {
2022-05-18 07:26:57 +00:00
const ns = new k8s.core.v1.Namespace('contour', {
metadata: {
name: 'contour',
},
});
const chartValues: ContourValues = {
configInline: {
// https://projectcontour.io/docs/main/configuration/
'accesslog-format': 'json',
// https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage
'json-fields': [
'@timestamp',
'bytes_received',
'bytes_sent',
'downstream_local_address',
'duration',
'method',
'path',
'request_id',
'response_code',
'response_flags',
'upstream_cluster',
'upstream_host',
'upstream_service_time',
'user_agent',
'x_forwarded_for',
],
},
contour: {
podAnnotations: {
'prometheus.io/scrape': 'true',
'prometheus.io/port': '8000',
'prometheus.io/scheme': 'http',
'prometheus.io/path': '/metrics',
2022-05-18 07:26:57 +00:00
},
podLabels: {
'vector.dev/exclude': 'true',
2022-05-18 07:26:57 +00:00
},
2024-03-26 08:18:00 +00:00
resources: {
limits: {},
},
},
envoy: {
2024-03-26 08:18:00 +00:00
resources: {
limits: {},
},
service: {
loadBalancerIP: this.staticIp?.address,
annotations:
this.staticIp?.address && this.staticIp?.aksReservedIpResourceGroup
2022-05-18 07:26:57 +00:00
? {
'service.beta.kubernetes.io/azure-load-balancer-resource-group':
this.staticIp?.aksReservedIpResourceGroup,
2022-05-18 07:26:57 +00:00
}
: undefined,
2022-05-18 07:26:57 +00:00
},
podAnnotations: {
'prometheus.io/scrape': 'true',
'prometheus.io/port': '8002',
'prometheus.io/scheme': 'http',
'prometheus.io/path': '/stats/prometheus',
},
autoscaling:
2024-03-26 08:18:00 +00:00
options?.envoy?.replicas && options.envoy.replicas > 1
? {
enabled: true,
minReplicas: 1,
2024-03-26 08:18:00 +00:00
maxReplicas: options.envoy.replicas,
}
: {},
2022-05-18 07:26:57 +00:00
},
};
2024-03-26 08:18:00 +00:00
if (options.envoy?.cpu) {
(chartValues.envoy!.resources!.limits as any).cpu = options.envoy.cpu;
}
if (options.envoy?.memory) {
(chartValues.envoy!.resources!.limits as any).memory = options.envoy.memory;
}
const proxyController = new k8s.helm.v3.Chart('contour-proxy', {
...CONTOUR_CHART,
namespace: ns.metadata.name,
// https://github.com/bitnami/charts/tree/master/bitnami/contour
values: chartValues,
2022-05-18 07:26:57 +00:00
});
this.lbService = proxyController.getResource('v1/Service', 'contour/contour-proxy-envoy');
2022-05-18 07:26:57 +00:00
const contourDeployment = proxyController.getResource(
'apps/v1/Deployment',
'contour/contour-proxy-contour',
);
new k8s.policy.v1.PodDisruptionBudget('contour-pdb', {
spec: {
minAvailable: 1,
selector: contourDeployment.spec.selector,
},
});
const envoyDaemonset = proxyController.getResource(
'apps/v1/ReplicaSet',
'contour/contour-proxy-envoy',
);
new k8s.policy.v1.PodDisruptionBudget('envoy-pdb', {
spec: {
minAvailable: 1,
selector: envoyDaemonset.spec.selector,
},
});
2022-05-18 07:26:57 +00:00
new k8s.apiextensions.CustomResource(
'secret-delegation',
{
apiVersion: 'projectcontour.io/v1',
kind: 'TLSCertificateDelegation',
metadata: {
name: this.tlsSecretName,
namespace: 'cert-manager',
},
spec: {
delegations: [
{
secretName: this.tlsSecretName,
targetNamespaces: ['*'],
},
],
},
},
{
dependsOn: [this.lbService],
},
2022-05-18 07:26:57 +00:00
);
return this;
}
get() {
return this.lbService;
}
}