Skip to content

Commit

Permalink
Merge branch 'master' into feat/cdk8s-argo-workflows-tde-904
Browse files Browse the repository at this point in the history
  • Loading branch information
paulfouquet committed Oct 29, 2023
2 parents ed64ad8 + 27e9752 commit a0eed92
Show file tree
Hide file tree
Showing 7 changed files with 412 additions and 269 deletions.
22 changes: 21 additions & 1 deletion infra/cdk8s.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import { App } from 'cdk8s';

import { ArgoSemaphore } from './charts/argo.semaphores';
import { ArgoWorkflows } from './charts/argo.workflows';
import { Cloudflared } from './charts/cloudflared';
import { FluentBit } from './charts/fluentbit';
import { Karpenter, KarpenterProvisioner } from './charts/karpenter';
import { CoreDns } from './charts/kube-system.coredns';
import { CfnOutputKeys, ClusterName } from './constants';
import { getCfnOutputs } from './util/cloud.formation';
import { fetchSsmParameters } from './util/ssm';

const app = new App();

Expand All @@ -22,6 +24,17 @@ async function main(): Promise<void> {
throw new Error(`Missing CloudFormation Outputs for keys ${missingKeys.join(', ')}`);
}

const ssmConfig = await fetchSsmParameters({
// Config for Cloudflared to access argo-server
tunnelId: '/eks/cloudflared/argo/tunnelId',
tunnelSecret: '/eks/cloudflared/argo/tunnelSecret',
tunnelName: '/eks/cloudflared/argo/tunnelName',
accountId: '/eks/cloudflared/argo/accountId',

// Personal access token to gain access to linz-basemaps github user
githubPat: '/eks/github/linz-basemaps/pat',
});

new ArgoSemaphore(app, 'semaphore', {});
const coredns = new CoreDns(app, 'dns', {});
const fluentbit = new FluentBit(app, 'fluentbit', {
Expand Down Expand Up @@ -49,11 +62,18 @@ async function main(): Promise<void> {
karpenterProvisioner.addDependency(karpenter);

new ArgoWorkflows(app, 'argo-workflows', {
clusterName,
clusterName: ClusterName,
saName: cfnOutputs[CfnOutputKeys.Argo.RunnerServiceAccountName],
tempBucketName: cfnOutputs[CfnOutputKeys.Argo.TempBucketName],
});

new Cloudflared(app, 'cloudflared', {
tunnelId: ssmConfig.tunnelId,
tunnelSecret: ssmConfig.tunnelSecret,
tunnelName: ssmConfig.tunnelName,
accountId: ssmConfig.accountId,
});

app.synth();
}

Expand Down
63 changes: 63 additions & 0 deletions infra/charts/cloudflared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Chart, ChartProps, Size } from 'cdk8s';
import * as kplus from 'cdk8s-plus-27';
import { Construct } from 'constructs';

import { applyDefaultLabels } from '../util/labels.js';

export class Cloudflared extends Chart {
constructor(
scope: Construct,
id: string,
props: { tunnelId: string; tunnelSecret: string; accountId: string; tunnelName: string } & ChartProps,
) {
super(scope, id, applyDefaultLabels(props, 'cloudflared', '2023.8.2', 'tunnel', 'workflows'));

// TODO should we create a new namespace every time
new kplus.Namespace(this, 'namespace', {
metadata: { name: props.namespace },
});

const cm = new kplus.ConfigMap(this, 'config', {
data: {
'config.yaml': [
`tunnel: ${props.tunnelName}`, // Tunnel name must match the credentials
'credentials-file: /etc/cloudflared/creds/credentials.json', // defined by "kplus.Secret" below
`metrics: "[::]:2000"`,
'no-autoupdate: true',
'protocol: http2', // quic is blocked in the LINZ network
].join('\n'),
},
});

// Secret credentials for the tunnel
const secret = new kplus.Secret(this, 'secret');
secret.addStringData(
'credentials.json',
JSON.stringify({
AccountTag: props.accountId,
TunnelID: props.tunnelId,
TunnelSecret: props.tunnelSecret,
}),
);

new kplus.Deployment(this, 'tunnel', {
// Ensure two tunnels are active
replicas: 2,
containers: [
{
name: 'cloudflared',
image: props.accountId + '.dkr.ecr.ap-southeast-2.amazonaws.com/eks:cloudflared-2023.8.2',
args: ['tunnel', '--loglevel', 'trace', '--config', '/etc/cloudflared/config/config.yaml', 'run'],
volumeMounts: [
{ volume: kplus.Volume.fromConfigMap(this, 'mount-config', cm), path: '/etc/cloudflared/config' },
{ volume: kplus.Volume.fromSecret(this, 'mount-secret', secret), path: '/etc/cloudflared/creds' },
],
resources: { memory: { request: Size.mebibytes(128) } },
// Cloudflared runs as root
securityContext: { ensureNonRoot: false },
},
],
securityContext: { ensureNonRoot: false },
});
}
}
4 changes: 2 additions & 2 deletions infra/charts/karpenter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export interface KarpenterProps {
*
* https://github.com/aws/karpenter/blob/7c989a2bfae43d4e73235aca0af50b8008c67b68/charts/karpenter/Chart.yaml#L5C10-L5C16
*/
const version = '0.31.0';
const version = 'v0.31.1';

export class Karpenter extends Chart {
constructor(scope: Construct, id: string, props: KarpenterProps & ChartProps) {
Expand Down Expand Up @@ -98,7 +98,7 @@ export class Karpenter extends Chart {

export class KarpenterProvisioner extends Chart {
constructor(scope: Construct, id: string, props: KarpenterProps & ChartProps) {
super(scope, id, applyDefaultLabels(props, 'karpenter', 'v0.31.0', 'karpenter', 'workflows'));
super(scope, id, applyDefaultLabels(props, 'karpenter', version, 'karpenter', 'workflows'));

const templateName = `karpenter-template`;
const template = new AwsNodeTemplate(this, 'template', {
Expand Down
15 changes: 3 additions & 12 deletions infra/util/cloud.formation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,12 @@ import { CloudFormation } from '@aws-sdk/client-cloudformation';
export async function getCfnOutputs(stackName: string): Promise<Record<string, string>> {
const cfn = new CloudFormation();
const searchStacks = await cfn.describeStacks({ StackName: stackName });
cfn.listExports;
const outputs: Record<string, string> = {};
const stacks = (searchStacks && searchStacks.Stacks) || [];
const stack = stacks.find((s) => s.StackName === stackName);
const stack = searchStacks?.Stacks?.find((s) => s.StackName === stackName);
if (stack?.Outputs == null) throw new Error(`Unable to find stack "${stackName}"`);

if (!stack) {
throw new Error(`Unable to find stack "${stackName}"`);
}
if (!stack.Outputs) {
throw new Error(`There is no output for stack "${stackName}"`);
}
stack.Outputs.forEach(({ OutputKey, OutputValue }) => {
if (OutputKey && OutputValue) {
outputs[OutputKey] = OutputValue;
}
if (OutputKey != null && OutputValue != null) outputs[OutputKey] = OutputValue;
});
return outputs;
}
35 changes: 35 additions & 0 deletions infra/util/ssm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { SSM } from '@aws-sdk/client-ssm';

const ssm = new SSM();

/**
* Attempt to load a collection of SSM parameters throwing if any parameter cannot be found
*
* @example
* ```typescript
* const result = fetchSsmParameters({ clientId: '/eks/client-id' })
* result.clientId // Value of '/eks/client-id'
* ```
*
* @throws if a parameter is missing from the store
*/
export async function fetchSsmParameters<T extends Record<string, string>>(query: T): Promise<T> {
console.log('FetchSSM', Object.values(query));
const ret = await ssm.getParameters({ Names: Object.values(query) });

const output: Record<string, string> = {};
const missing: string[] = [];
for (const [key, parameterName] of Object.entries(query)) {
const val = ret.Parameters?.find((f) => f.Name === parameterName);
if (val == null || val.Value == null) {
missing.push(parameterName);
continue;
}
output[key] = val.Value;
}

if (missing.length > 0) {
throw new Error('Missing SSM Parameters: ' + missing.join(', '));
}
return output as T;
}
Loading

0 comments on commit a0eed92

Please sign in to comment.