Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initiate CDK Pipeline #92

Merged
merged 21 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,6 @@ cdk.context.json
data/

Brewfile.lock.json
*.xml

target/
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,17 @@ baseline:
test:
@yarn test

# Test only section
test-stateful:
@yarn run test ./test/stateful
test-stateless:
@yarn run test ./test/stateless

# Run all test suites - i.e. cdk app unit tests + each microservice app test suites
# Each app root should have Makefile `test` target; that run your app test pipeline including compose stack up/down
# Note by running `make suite` target from repo root means your local dev env is okay with all app toolchains i.e.
# Python (conda or venv), Rust and Cargo, TypeScript and Node environment, Docker and Container runtimes
suite: test
suite: test-stateless
@(cd lib/workload/stateless/sequence_run_manager && $(MAKE) test)
@(cd lib/workload/stateless/metadata_manager && $(MAKE) test)
@#(cd lib/workload/stateless/filemanager && $(MAKE) test) # FIXME uncomment when ready @Marko
Expand Down
31 changes: 15 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,27 @@ Please note; this is the _INVERSE_ of some typical standalone project setup such

In this repo, we flip this view such that the Git repo root is the TypeScript CDK project; that wraps our applications into `./lib/` directory. You may [sparse checkout](https://git-scm.com/docs/git-sparse-checkout) or directly open subdirectory to set up the application project alone if you wish; e.g. `webstorm lib/workload/stateless/metadata_manager` or `code lib/workload/stateless/metadata_manager` or `pycharm lib/workload/stateless/sequence_run_manager` or `rustrover lib/workload/stateless/filemanager`. However, `code .` is a CDK TypeScript project.

This root level CDK app contains 3 major stacks: `pipeline`, `stateful` and `stateless`. Pipeline stack is the CI/CD automation with CodePipeline setup. The `stateful` stack holds and manages some long-running AWS infrastructure resources. The `stateless` stack manages self-mutating CodePipeline reusable CDK Constructs for the [MicroService Applications](docs/developer/MICROSERVICE.md). In terms of CDK deployment point-of-view, the microservice application will be "stateless" application such that it will be changing/mutating over time; whereas "the data" its holds like PostgreSQL server infrastructure won't be changing that frequent. When updating "stateful" resources, there involves additional cares, steps and ops-procedures such as backing up database, downtime planning and so on; hence stateful. We use [configuration constants](./config) to decouple the reference between `stateful` and `stateless` AWS resources.
This root level CDK app contains 4 major stacks: `stateful-pipeline`,`stateless-pipeline` , `stateful` and `stateless`. Pipeline stack is the CI/CD automation with CodePipeline setup. The `stateful` stack holds and manages some long-running AWS infrastructure resources. The `stateless` stack manages self-mutating CodePipeline reusable CDK Constructs for the [MicroService Applications](docs/developer/MICROSERVICE.md). In terms of CDK deployment point-of-view, the microservice application will be "stateless" application such that it will be changing/mutating over time; whereas "the data" its holds like PostgreSQL server infrastructure won't be changing that frequent. When updating "stateful" resources, there involves additional cares, steps and ops-procedures such as backing up database, downtime planning and so on; hence stateful. We use [configuration constants](./config) to decouple the reference between `stateful` and `stateless` AWS resources.

In most cases, we deploy with automation across operational target environments or AWS accounts: `beta`, `gamma`, `prod`. For some particular purpose (such as onboarding procedure, isolated experimentation), we can spin up the whole infrastructure into some unique isolated AWS account. These key CDK entrypoints are documented in the following sections: Automation and Manual.

### Automation

_CI/CD through CodePipeline automation from AWS toolchain account_

There are 2 pipeline stacks in this project, one for the stateful and one for the stateless stack deployment. There is a
script to access the `cdk` command for each pipeline:
-`cdk-stateless-pipeline` - for stateless pipeline
-`cdk-stateful-pipeline` - for stateful pipeline

```
make install
make check
make test

yarn cdk list

yarn cdk synth <StackName>
yarn cdk diff <StackName>
yarn cdk deploy <StackName>

yarn cdk synth
yarn cdk diff
yarn cdk deploy --all
yarn cdk-stateless-pipeline synth
yarn cdk-stateless-pipeline diff
yarn cdk-stateless-pipeline deploy
```

### Manual
Expand All @@ -47,12 +46,12 @@ make test

yarn orcabus --help

yarn orcabus list
yarn orcabus synth OrcaBusStatefulStack
yarn orcabus diff OrcaBusStatefulStack
yarn orcabus deploy OrcaBusStatefulStack
yarn orcabus deploy --all
yarn orcabus destroy --all
yarn cdk-orcabus list
yarn cdk-orcabus synth OrcaBusStatefulStack
yarn cdk-orcabus diff OrcaBusStatefulStack
yarn cdk-orcabus deploy OrcaBusStatefulStack
yarn cdk-orcabus deploy --all
yarn cdk-orcabus destroy --all
```

## Development
Expand Down
4 changes: 2 additions & 2 deletions bin/orcabus.ts
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import { getEnvironmentConfig } from '../config/constants';
const app = new cdk.App();
const props: cdk.StackProps = {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
account: process.env.CDK_DEPLOY_ACCOUNT || process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEPLOY_REGION || process.env.CDK_DEFAULT_REGION,
},
tags: {
'umccr-org:Stack': 'OrcaBusSandboxApp',
Expand Down
6 changes: 3 additions & 3 deletions bin/pipeline.ts → bin/stateful-pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@
import 'source-map-support/register';

import * as cdk from 'aws-cdk-lib';
import { PipelineStack } from '../lib/pipeline/orcabus-pipeline-stack';
import { StatefulPipelineStack } from '../lib/pipeline/orcabus-stateful-pipeline-stack';

const AWS_TOOLCHAIN_ACCOUNT = '383856791668'; // Bastion
const AWS_TOOLCHAIN_REGION = 'ap-southeast-2';

const app = new cdk.App();

new PipelineStack(app, `OrcaBusPipeline`, {
new StatefulPipelineStack(app, `OrcaBusStatefulPipeline`, {
env: {
account: AWS_TOOLCHAIN_ACCOUNT,
region: AWS_TOOLCHAIN_REGION,
},
tags: {
'umccr-org:Stack': 'OrcaBusPipelineApp',
'umccr-org:Stack': 'OrcaBusStatefulPipelineApp',
'umccr-org:Product': 'OrcaBus',
},
});
21 changes: 21 additions & 0 deletions bin/stateless-pipeline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env node
import 'source-map-support/register';

import * as cdk from 'aws-cdk-lib';
import { StatelessPipelineStack } from '../lib/pipeline/orcabus-stateless-pipeline-stack';

const AWS_TOOLCHAIN_ACCOUNT = '383856791668'; // Bastion
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the Bastion account?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We opt to CICD within the CodePipeline. So, the GHA PR build will only do pre-flight checks - lint, format, secret-scan, dependencies audits.

Choosing the Bastion account because we still opt with our AWS accounts. There, Bastion account has been repurposed as build/toolchain account.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed; will reenable GHA PR Build with #100

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also will tear down and re-deploy into UoM AWS accounts with #102

const AWS_TOOLCHAIN_REGION = 'ap-southeast-2';

const app = new cdk.App();

new StatelessPipelineStack(app, `OrcaBusStatelessPipeline`, {
env: {
account: AWS_TOOLCHAIN_ACCOUNT,
region: AWS_TOOLCHAIN_REGION,
},
tags: {
'umccr-org:Stack': 'OrcaBusStatelessPipeline',
'umccr-org:Product': 'OrcaBus',
},
});
2 changes: 1 addition & 1 deletion cdk.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"app": "npx ts-node --prefer-ts-exts bin/pipeline.ts",
"app": "yarn run -B ts-node --prefer-ts-exts bin/orcabus.ts",
"watch": {
"include": [
"**"
Expand Down
13 changes: 8 additions & 5 deletions config/constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { OrcaBusStatefulConfig } from '../lib/workload/orcabus-stateful-stack';
import { AuroraPostgresEngineVersion } from 'aws-cdk-lib/aws-rds';
import { OrcaBusStatelessConfig } from '../lib/workload/orcabus-stateless-stack';
import { Duration, aws_lambda } from 'aws-cdk-lib';
import { Duration, aws_lambda, RemovalPolicy } from 'aws-cdk-lib';

const regName = 'OrcaBusSchemaRegistry';
const eventBusName = 'OrcaBusMain';
Expand All @@ -24,7 +24,7 @@ const orcaBusStatefulConfig = {
defaultDatabaseName: 'orcabus',
version: AuroraPostgresEngineVersion.VER_15_4,
parameterGroupName: 'default.aurora-postgresql15',
username: 'admin',
username: 'postgres',
dbPort: 5432,
masterSecretName: rdsMasterSecretName,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to create per app/service db secret manager record per database to isolate them better. Instead of reusing this master (SA account). Wonder, we can do this arrangement straight up now at this point... By chance, do you happen to figure any solution to it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that we should isolate on a per service level, but that means the stateless stack needs to be aware of the consuming services, right?
And any database required by any service would have to be registered to and created by the stateless stack IIRC. The stateless stack can create "user" credentials for each service, but I think they'd need to be linked to existing DBs? I don't have the understanding yet of what's possible...

I think the initial thinking here was to allow access on the cluster level to start with in order to not block any service development. Once we've figured out a better way, we could refactor.

Or do you think it's worth working that out now and safe on the refactor ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend we should work this out now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree in working this out. Could be wrong, but it doesn't look like CDK can create more than one database/user per cluster using the regular higher level constructs (see issue aws/aws-cdk#13588)? Might have to create a migration-style Lambda function which executes create database ... after the RDS cluster is up and running, e.g. using something like this. Then, I think each service would need to register their preferred database name, user and credentials somewhere (in constants.ts?).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or, each service could register their own migration function to create the databases/users? That way the databases wouldn't have to be registered somewhere higher up like in constants.ts. Not sure if this makes more sense?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I wonder if the stateless stack could create IAM roles/policies for each service that would allow these services access to only their specific DB(s) on the instance. E.g. passing on the permissions to do what the service needs to do (without control/knowledge of what that may be), as long as it happens in the designated DB.

I haven't found any examples, but it looks like you can create IAM policies with conditions to restrict access by rds:DatabaseName.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will track this at issue #99

monitoring: {
Expand Down Expand Up @@ -91,9 +91,10 @@ export const getEnvironmentConfig = (
...orcaBusStatefulConfig.databaseProps,
numberOfInstance: 1,
minACU: 0.5,
maxACU: 1,
maxACU: 16,
enhancedMonitoringInterval: Duration.seconds(60),
enablePerformanceInsights: true,
removalPolicy: RemovalPolicy.DESTROY,
},
securityGroupProps: {
...orcaBusStatefulConfig.securityGroupProps,
Expand All @@ -120,9 +121,10 @@ export const getEnvironmentConfig = (
...orcaBusStatefulConfig.databaseProps,
numberOfInstance: 1,
minACU: 0.5,
maxACU: 1,
maxACU: 16,
enhancedMonitoringInterval: Duration.seconds(60),
enablePerformanceInsights: true,
removalPolicy: RemovalPolicy.DESTROY,
},
securityGroupProps: {
...orcaBusStatefulConfig.securityGroupProps,
Expand All @@ -149,7 +151,8 @@ export const getEnvironmentConfig = (
...orcaBusStatefulConfig.databaseProps,
numberOfInstance: 1,
minACU: 0.5,
maxACU: 1,
maxACU: 16,
removalPolicy: RemovalPolicy.RETAIN,
},
securityGroupProps: {
...orcaBusStatefulConfig.securityGroupProps,
Expand Down
10 changes: 10 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,14 @@ module.exports = {
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
reporters: [
'default',
[
'jest-junit',
{
outputDirectory: 'target/report',
outputName: 'infrastructureTest.xml',
},
],
],
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ import * as cdk from 'aws-cdk-lib';
import * as ssm from 'aws-cdk-lib/aws-ssm';
import * as pipelines from 'aws-cdk-lib/pipelines';
import * as codebuild from 'aws-cdk-lib/aws-codebuild';
import { OrcaBusStatelessConfig, OrcaBusStatelessStack } from '../workload/orcabus-stateless-stack';
import * as iam from 'aws-cdk-lib/aws-iam';
import { OrcaBusStatefulConfig, OrcaBusStatefulStack } from '../workload/orcabus-stateful-stack';
import { getEnvironmentConfig } from '../../config/constants';
import { PolicyStatement } from 'aws-cdk-lib/aws-iam';

export class PipelineStack extends cdk.Stack {
export class StatefulPipelineStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: cdk.StackProps) {
super(scope, id, props);

Expand All @@ -18,34 +17,61 @@ export class PipelineStack extends cdk.Stack {
connectionArn: codeStarArn,
});

const synthAction = new pipelines.CodeBuildStep('Synth', {
const unitTest = new pipelines.CodeBuildStep('UnitTest', {
commands: ['yarn install --frozen-lockfile', 'make test-stateful'],
input: sourceFile,
commands: ['yarn install --frozen-lockfile', 'yarn cdk synth -v'],
primaryOutputDirectory: '.',
buildEnvironment: {
privileged: true,
computeType: codebuild.ComputeType.LARGE,
buildImage: codebuild.LinuxArmBuildImage.AMAZON_LINUX_2_STANDARD_3_0,
environmentVariables: {
NODE_OPTIONS: {
value: '--max-old-space-size=8192',
},
},
},
partialBuildSpec: codebuild.BuildSpec.fromObject({
reports: {
'orcabus-infrastructureStatefulReports': {
files: ['target/report/*.xml'],
'file-format': 'JUNITXML',
},
},
version: '0.2',
}),
});

const synthAction = new pipelines.CodeBuildStep('Synth', {
commands: ['yarn install --frozen-lockfile', 'yarn run cdk-stateful-pipeline synth'],
input: unitTest,
primaryOutputDirectory: 'cdk.out',
rolePolicyStatements: [
new PolicyStatement({
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['sts:AssumeRole'],
resources: ['*'],
conditions: {
StringEquals: {
'iam:ResourceTag/aws-cdk:bootstrap-role': 'lookup',
},
},
}),
],
});
synthAction.addStepDependency(new OrcaBusTestStep('OrcaBusUnitTest', { source: sourceFile }));

const pipeline = new pipelines.CodePipeline(this, 'Pipeline', {
synth: synthAction,
selfMutation: true,
crossAccountKeys: true,
dockerEnabledForSynth: true,
dockerEnabledForSelfMutation: true,
codeBuildDefaults: {
buildEnvironment: {
buildImage: codebuild.LinuxBuildImage.STANDARD_6_0,
computeType: codebuild.ComputeType.LARGE,
buildImage: codebuild.LinuxArmBuildImage.AMAZON_LINUX_2_STANDARD_3_0,
environmentVariables: {
NODE_OPTIONS: {
value: '--max-old-space-size=8192',
},
},
},
},
dockerEnabledForSelfMutation: true,
});

/**
Expand All @@ -54,7 +80,7 @@ export class PipelineStack extends cdk.Stack {
const betaConfig = getEnvironmentConfig('beta');
if (!betaConfig) throw new Error(`No 'Beta' account configuration`);
pipeline.addStage(
new OrcaBusDeploymentStage(this, 'BetaDeployment', betaConfig.stackProps, {
new OrcaBusStatefulDeploymentStage(this, 'BetaDeployment', betaConfig.stackProps, {
account: betaConfig.accountId,
})
);
Expand All @@ -65,7 +91,7 @@ export class PipelineStack extends cdk.Stack {
const gammaConfig = getEnvironmentConfig('gamma');
if (!gammaConfig) throw new Error(`No 'Gamma' account configuration`);
pipeline.addStage(
new OrcaBusDeploymentStage(this, 'GammaDeployment', gammaConfig.stackProps, {
new OrcaBusStatefulDeploymentStage(this, 'GammaDeployment', gammaConfig.stackProps, {
account: gammaConfig.accountId,
}),
{ pre: [new pipelines.ManualApprovalStep('PromoteToGamma')] }
Expand All @@ -74,65 +100,28 @@ export class PipelineStack extends cdk.Stack {
/**
* Deployment to Prod account (DISABLED)
*/
// const prodConfig = getEnvironmentConfig('prod');
// if (!prodConfig) throw new Error(`No 'Prod' account configuration`);
// pipeline.addStage(
// new OrcaBusDeploymentStage(this, 'prodDeployment', prodConfig.stackProps, {
// account: gammaConfig?.accountId,
// }),
// { pre: [new pipelines.ManualApprovalStep('PromoteToProd')] }
// );
const prodConfig = getEnvironmentConfig('prod');
if (!prodConfig) throw new Error(`No 'Prod' account configuration`);
pipeline.addStage(
new OrcaBusStatefulDeploymentStage(this, 'prodDeployment', prodConfig.stackProps, {
account: gammaConfig?.accountId,
}),
{ pre: [new pipelines.ManualApprovalStep('PromoteToProd')] }
);
}
}

class OrcaBusDeploymentStage extends cdk.Stage {
class OrcaBusStatefulDeploymentStage extends cdk.Stage {
constructor(
scope: Construct,
environmentName: string,
stackProps: {
orcaBusStatefulConfig: OrcaBusStatefulConfig;
orcaBusStatelessConfig: OrcaBusStatelessConfig;
},
env?: cdk.Environment
) {
super(scope, environmentName, { env: { account: env?.account, region: 'ap-southeast-2' } });

new OrcaBusStatefulStack(this, 'OrcaBusStatefulStack', stackProps.orcaBusStatefulConfig);
new OrcaBusStatelessStack(this, 'OrcaBusStatelessStack', stackProps.orcaBusStatelessConfig);
}
}

interface OrcaBusTestStepProps {
source: pipelines.CodePipelineSource;
}
class OrcaBusTestStep extends pipelines.CodeBuildStep {
constructor(id: string, props: OrcaBusTestStepProps) {
const stepProps: pipelines.CodeBuildStepProps = {
input: props.source,
commands: [],
partialBuildSpec: codebuild.BuildSpec.fromObject({
env: {
shell: 'bash',
},
phases: {
install: {
commands: [
'wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-py310_23.3.1-0-Linux-x86_64.sh',
'bash Miniconda3-py310_23.3.1-0-Linux-x86_64.sh -b',
'export PATH=/root/miniconda3/bin:$PATH',
'conda init bash',
'source activate',
'conda create -q -n orcabus python=3.10',
'conda run -n orcabus yarn install',
],
},
build: {
commands: ['conda run -n orcabus make test'],
},
},
version: '0.2',
}),
};
super(id, stepProps);
}
}
Loading
Loading