Skip to content

Commit

Permalink
feat: Fluentbit TDE-913 (#195)
Browse files Browse the repository at this point in the history
Allows fluentbit deployment via `cdk8s`

---------

Co-authored-by: Paul Fouquet <[email protected]>
  • Loading branch information
MDavidson17 and paulfouquet authored Oct 26, 2023
1 parent 0f51b8b commit 0cd0914
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 18 deletions.
12 changes: 12 additions & 0 deletions config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ However, some of the component Helm charts do not have a `values.schema.json`. F
- aws-for-fluent-bit (<https://github.com/aws/eks-charts/issues/1011>)
- Karpenter

### Working with Helm charts

#### Generate code

It is possible to generate a specific Helm construct for the component if their chart includes a `value.schema.json`. This is useful to provide typing hints when specifying their configuration (<https://github.com/cdk8s-team/cdk8s/blob/master/docs/cli/import.md#values-schema>)

To generate the Helm Construct for a specific Chart, follow the instructions [here](https://github.com/cdk8s-team/cdk8s/blob/master/docs/cli/import.md#values-schema)

However, some of the component Helm charts do not have a `values.schema.json`. For those we won't generate any code and use the default `Helm` construct:

- aws-for-fluent-bit (<https://github.com/aws/eks-charts/issues/1011>)

## Usage (for test)

Ensure all dependencies are installed
Expand Down
4 changes: 2 additions & 2 deletions config/cdk.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { App } from 'aws-cdk-lib';

import { CLUSTER_NAME } from './constants';
import { ClusterName } from './constants';
import { LinzEksCluster } from './eks/cluster';

const app = new App();

async function main(): Promise<void> {
new LinzEksCluster(app, CLUSTER_NAME, {
new LinzEksCluster(app, ClusterName, {
env: { region: 'ap-southeast-2', account: process.env.CDK_DEFAULT_ACCOUNT },
});

Expand Down
21 changes: 14 additions & 7 deletions config/cdk8s.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,42 @@
import { App } from 'cdk8s';

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

const app = new App();

async function main(): Promise<void> {
// Get cloudformation outputs
const cfnOutputs = await getCfnOutputs(CLUSTER_NAME);
const missingKeys = [...Object.values(CfnOutputKeys.Karpenter)].filter((f) => cfnOutputs[f] == null);
const cfnOutputs = await getCfnOutputs(ClusterName);
const missingKeys = [...Object.values(CfnOutputKeys.Karpenter), ...Object.values(CfnOutputKeys.FluentBit)].filter(
(f) => cfnOutputs[f] == null,
);
if (missingKeys.length > 0) {
throw new Error(`Missing CloudFormation Outputs for keys ${missingKeys.join(', ')}`);
}

new ArgoSemaphore(app, 'semaphore', {});
new CoreDns(app, 'Dns', {});
const coredns = new CoreDns(app, 'dns', {});
const fluentbit = new FluentBit(app, 'fluentbit', {
saRoleName: cfnOutputs[CfnOutputKeys.FluentBit.ServiceAccountName],
clusterName: ClusterName,
});
fluentbit.addDependency(coredns);

const karpenter = new Karpenter(app, 'karpenter', {
clusterName: CLUSTER_NAME,
clusterName: ClusterName,
clusterEndpoint: cfnOutputs[CfnOutputKeys.Karpenter.ClusterEndpoint],
saRoleName: cfnOutputs[CfnOutputKeys.Karpenter.ServiceAccountName],
saRoleArn: cfnOutputs[CfnOutputKeys.Karpenter.ServiceAccountRoleArn],
instanceProfile: cfnOutputs[CfnOutputKeys.Karpenter.DefaultInstanceProfile],
});

const karpenterProvisioner = new KarpenterProvisioner(app, 'karpenter-provisioner', {
clusterName: CLUSTER_NAME,
clusterName: ClusterName,
clusterEndpoint: cfnOutputs[CfnOutputKeys.Karpenter.ClusterEndpoint],
saRoleName: cfnOutputs[CfnOutputKeys.Karpenter.ServiceAccountName],
saRoleArn: cfnOutputs[CfnOutputKeys.Karpenter.ServiceAccountRoleArn],
Expand Down
71 changes: 71 additions & 0 deletions config/charts/fluentbit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Chart, ChartProps, Helm } from 'cdk8s';
import { Construct } from 'constructs';

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

/** version of the Helm chart (not FluentBit app) */
const awsForFluentBitVersion = '0.1.31';
export interface FluentBitProps {
saRoleName: string;
clusterName: string;
}

export class FluentBit extends Chart {
constructor(scope: Construct, id: string, props: FluentBitProps & ChartProps) {
super(scope, id, applyDefaultLabels(props, 'aws-for-fluent-bit', awsForFluentBitVersion, 'logs', 'workflows'));

const FluentParserName = 'containerd';
// This needs to be properly formatted, and it was taken directly from https://github.com/microsoft/fluentbit-containerd-cri-o-json-log
// The key part is the message must be parsed as "log" otherwise it wont be parsed as JSON
const extraParsers = `[PARSER]
Name ${FluentParserName}
Format regex
Regex ^(?<time>[^ ]+) (?<stream>stdout|stderr) (?<logtag>[^ ]*) (?<log>.*)$
Time_Key time
Time_Format %Y-%m-%dT%H:%M:%S.%L%z
`;

/**
* FIXME: We deactivated the HTTP server to avoid getting this issue:
* https://github.com/aws/eks-charts/issues/983
*
*/
const extraService = `
HTTP_Server Off
HTTP_Listen [::]
HTTP_PORT 2020
Health_Check On
HC_Errors_Count 5
HC_Retry_Failure_Count 5
HC_Period 5
`;

new Helm(this, 'aws-for-fluent-bit', {
chart: 'aws-for-fluent-bit',
repo: 'https://aws.github.io/eks-charts',
namespace: 'fluentbit',
version: awsForFluentBitVersion,
values: {
fullnameOverride: 'fluentbit',
input: { parser: FluentParserName, dockerMode: 'Off' },
serviceAccount: { name: props.saRoleName, create: false },
cloudWatchLogs: {
enabled: true,
region: 'ap-southeast-2',
autoCreateGroup: true,
logRetentionDays: 30,
logGroupName: `/aws/eks/${props.clusterName}/logs`,
logGroupTemplate: `/aws/eks/${props.clusterName}/workload/$kubernetes['namespace_name']`,
logStreamPrefix: 'fb-',
},
firehose: { enabled: false },
kinesis: { enabled: false },
elasticsearch: { enabled: false },
service: { extraParsers, extraService },
// FIXME: `livenessProbe` and `readinessProbe` deactivated https://github.com/aws/eks-charts/issues/995
livenessProbe: false,
readinessProbe: false,
},
});
}
}
1 change: 1 addition & 0 deletions config/charts/karpenter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export class Karpenter extends Chart {
namespace: 'karpenter',
version: 'v0.31.1',
values: {
fullnameOverride: 'karpenter', // override the karpenter-abcxywz
serviceAccount: {
create: false,
name: props.saRoleName,
Expand Down
5 changes: 4 additions & 1 deletion config/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* Cluster name */
export const CLUSTER_NAME = 'Workflows';
export const ClusterName = 'Workflows';

/* CloudFormation Output to access from CDK8s */
export const CfnOutputKeys = {
Expand All @@ -9,4 +9,7 @@ export const CfnOutputKeys = {
ClusterEndpoint: 'ClusterEndpoint',
DefaultInstanceProfile: 'DefaultInstanceProfile',
},
FluentBit: {
ServiceAccountName: 'FluentBitServiceAccountName',
},
};
19 changes: 12 additions & 7 deletions config/eks/cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import {
Role,
ServicePrincipal,
} from 'aws-cdk-lib/aws-iam';
import { IBucket } from 'aws-cdk-lib/aws-s3';
import { BlockPublicAccess, Bucket } from 'aws-cdk-lib/aws-s3';
import { BlockPublicAccess, Bucket, IBucket } from 'aws-cdk-lib/aws-s3';
import { Construct } from 'constructs';

import { CfnOutputKeys } from '../constants';
Expand Down Expand Up @@ -138,7 +137,7 @@ export class LinzEksCluster extends Stack {
);

// Allow Karpenter to start ec2 instances
// FIXME: some policies are missing. See https://github.com/aws/karpenter/blob/8c33a40733b90aa0bb42a6436152374f7b359f69/website/content/en/docs/getting-started/getting-started-with-karpenter/cloudformation.yaml#L40
// @see https://github.com/aws/karpenter/blob/8c33a40733b90aa0bb42a6436152374f7b359f69/website/content/en/docs/getting-started/getting-started-with-karpenter/cloudformation.yaml#L40
// The current policies are based on https://github.com/eksctl-io/eksctl/blob/main/pkg/cfn/builder/karpenter_test.go#L111
new Policy(this, 'ControllerPolicy', {
roles: [serviceAccount.role],
Expand Down Expand Up @@ -192,14 +191,20 @@ export class LinzEksCluster extends Stack {
const fluentBitNs = this.cluster.addManifest('FluentBitNamespace', {
apiVersion: 'v1',
kind: 'Namespace',
metadata: { name: 'fluent-bit' },
metadata: { name: 'fluentbit' },
});
const fluentBitSa = this.cluster.addServiceAccount('FluentBitServiceAccount', {
name: 'fluent-bit-sa',
namespace: 'fluent-bit',
name: 'fluentbit-sa',
namespace: 'fluentbit',
});
// https://docs.aws.amazon.com/aws-managed-policy/latest/reference/CloudWatchAgentServerPolicy.html
fluentBitSa.role.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName('CloudWatchAgentServerPolicy'));
fluentBitSa.role.addToPrincipalPolicy(
new PolicyStatement({ actions: ['logs:PutRetentionPolicy'], resources: ['*'], effect: Effect.ALLOW }),
);
fluentBitSa.node.addDependency(fluentBitNs); // Ensure the namespace created first
new CfnOutput(this, 'FluentBitServiceAccountRoleArn', { value: fluentBitSa.role.roleArn });

new CfnOutput(this, CfnOutputKeys.FluentBit.ServiceAccountName, { value: fluentBitSa.serviceAccountName });

// Basic constructs for argo to be deployed into
const argoNs = this.cluster.addManifest('ArgoNameSpace', {
Expand Down
2 changes: 1 addition & 1 deletion docs/dns.configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ file: index.mjs
```javascript
fetch('https://google.com').then((c) => console.log(c));

import * as dns from 'dns/promises'
import * as dns from 'dns/promises';

await dns.resolve('google.com', 'A');
await dns.resolve('google.com', 'AAAA');
Expand Down
18 changes: 18 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 0cd0914

Please sign in to comment.