Skip to content

Commit

Permalink
Merge pull request #1 from brucehoff/dev
Browse files Browse the repository at this point in the history
PLFM-8554:  Run Synapse Docker registry using ECS
  • Loading branch information
brucehoff authored Oct 21, 2024
2 parents 3a577c6 + 2520b20 commit adb2fdc
Show file tree
Hide file tree
Showing 17 changed files with 338 additions and 182 deletions.
29 changes: 14 additions & 15 deletions .github/workflows/aws-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,17 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v3

# Uncomment for deployment to AWS
# - name: Assume AWS Role
# uses: aws-actions/configure-aws-credentials@v1
# with:
# aws-region: us-east-1
# role-to-assume: ${{inputs.ROLE_TO_ASSUME }}
# role-session-name: GitHubActions-${{ github.repository_owner }}-${{ github.event.repository.name }}-${{ github.run_id }}
# role-duration-seconds: 1200
#
# - name: cdk deploy
# uses: youyo/aws-cdk-github-actions@v2
# with:
# cdk_subcommand: 'deploy'
# cdk_args: '--require-approval never --context env=${{ inputs.CONTEXT }} --progress events --debug'
# actions_comment: false
- name: Assume AWS Role
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: us-east-1
role-to-assume: ${{inputs.ROLE_TO_ASSUME }}
role-session-name: GitHubActions-${{ github.repository_owner }}-${{ github.event.repository.name }}-${{ github.run_id }}
role-duration-seconds: 1200

- name: cdk deploy
uses: youyo/aws-cdk-github-actions@v2
with:
cdk_subcommand: 'deploy'
cdk_args: '--require-approval never --context env=${{ inputs.CONTEXT }} --progress events --debug'
actions_comment: false
32 changes: 16 additions & 16 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,32 +25,32 @@ jobs:
- name: Run unit tests
run: python -m pytest tests/ -s -v

synth-test-dev:
# make sure tests pass for both dev and prod stacks
synth-test:
needs: tests
strategy:
matrix:
stack: ['dev', 'prod']
uses: "./.github/workflows/synth-test.yml"
with:
CONTEXT: dev

synth-test-prod:
needs: tests
uses: "./.github/workflows/synth-test.yml"
with:
CONTEXT: prod
CONTEXT: ${{ matrix.stack }}

cdk-deploy-dev:
if: ${{github.ref == 'refs/heads/dev'}}
needs: [tests, synth-test-dev]
# only run on a push/merge, not on a PR
if: ${{ github.ref_name == 'dev' }}
needs: [tests, synth-test]
uses: "./.github/workflows/aws-deploy.yml"
secrets: inherit
with:
CONTEXT: dev
ROLE_TO_ASSUME: arn:aws:iam::<dev-account-number>:role/<dev-role-name>
CONTEXT: ${{ github.ref_name }}
ROLE_TO_ASSUME: arn:aws:iam::449435941126:role/sagebase-github-oidc-sage-bionetworks-synapse-docker-registry

cdk-deploy-prod:
if: ${{github.ref == 'refs/heads/prod'}}
needs: [tests, synth-test-prod]
# only run on a push/merge, not on a PR
if: ${{ github.ref_name == 'prod' }}
needs: [tests, synth-test]
uses: "./.github/workflows/aws-deploy.yml"
secrets: inherit
with:
CONTEXT: prod
ROLE_TO_ASSUME: arn:aws:iam::<production-account-number>:role/<production-role-name>
CONTEXT: ${{ github.ref_name }}
ROLE_TO_ASSUME: arn:aws:iam::325565585839:role/sagebase-github-oidc-sage-bionetworks-synapse-docker-registry
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,7 @@ cdk.out/

# VS Code
.vscode/

# locally generated self-signed certificate-key pair
certificate.pem
privatekey.pem
4 changes: 4 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,7 @@ repos:
hooks:
- id: check-github-workflows
- id: check-github-actions
- repo: https://github.com/hadolint/hadolint
rev: v2.12.0
hooks:
- id: hadolint-docker
20 changes: 20 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#
# Creates a configuration of the open source Docker registry
# suitable for using with Synapse
#
#
FROM registry:2
# stack is dev or prod
ARG stack=dev

# switches between 'dev' and 'prod'
COPY resources/${stack}/config.yml /etc/docker/registry/config.yml
COPY resources/${stack}/token_signing_key_public_cert.pem /etc/docker/registry/token_signing_key_public_cert.pem
# the self-signed cert' and private key should be generated before running 'docker build'
# see the github workflow for an example of how to do this
COPY privatekey.pem /etc/docker/registry/ssl/privatekey.pem
COPY certificate.pem /etc/docker/registry/ssl/certificate.pem

COPY startup.sh /startup.sh

ENTRYPOINT ["/startup.sh"]
144 changes: 33 additions & 111 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,56 +1,56 @@
# aws-application-deployment-template
# synapse-docker-registry

CDK-based template for deploying a containerized application to AWS
CDK-based template for deploying the Synapse Docker registry

## Perequisites

AWS CDK projects require some bootstrapping before synthesis or deployment.
Please review the [bootstapping documentation](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html#getting_started_bootstrap)
before development.
## Configuration

## Development

The `cdk.json` file tells the CDK Toolkit how to execute your app.
We use the [AWS Secrets Manager](https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html)

This project is set up like a standard Python project. The initialization
process also creates a virtualenv within this project, stored under the `.venv`
directory. To create the virtualenv it assumes that there is a `python3`
(or `python` for Windows) executable in your path with access to the `venv`
package. If for any reason the automatic creation of the virtualenv fails,
you can create the virtualenv manually.
In dev the secret is named `registry-dev-DockerFargateStack/dev/ecs` and in the prod stack,
`registry-prod-DockerFargateStack/prod/ecs`

To manually create a virtualenv on MacOS and Linux:
A secret is a collection of key-value pairs. For this application there is just one pair. The key should be `notification_auth` and the value is the
Base64 encoded "Basic auth" credentials which are a shared-secret with Synapse as the event notification recipient.

```
$ python3 -m venv .venv
```
### Registry container
We use the open source Docker `registry`, available on DockerHub. This container requires several configuration files to be mounted.
To achieve this cleanly, we embed the files into our own copy of the registry, created at synth' time, on the fly.

After the init process completes and the virtualenv is created, you can use the following
step to activate your virtualenv.
To build the container image manually during development:

```
$ source .venv/bin/activate
```
openssl req -x509 -days 3650 -newkey rsa:2048 -sha256 \
-nodes -keyout privatekey.pem -out certificate.pem \
-subj "/C=US/ST=WA/L=Seattle/O=SageBionetworks/OU=IT/CN=www.synapse.org"
If you are a Windows platform, you would activate the virtualenv like this:
docker build --build-arg stack=dev .
```
% .venv\Scripts\activate.bat
```

Once the virtualenv is activated, you can install the required dependencies.
Note that when deployed the container is behind a load balancer which uses its own certificate. The self-signed-cert'
only serves to allow encryption of data in transit between the load balancer and the container, which is a HIPAA
security requirement.

```
$ pip install -r requirements.txt
```
### Missing Secrets

At this point you can now synthesize the CloudFormation template for this code.
An environment context is rquired, check [cdk.json](cdk.json) for available contexts.
Each new environment (dev/staging/prod/etc..) requires adding secrets in AWS Secrets Manager. If a
secret is not created for the environment you may get an error with the following stack trace:

```
$ cdk synth --context env=dev
Resource handler returned message: "Error occurred during operation 'ECS Deployment Circuit Breaker was triggered'." (RequestToken: d180e115-ba94-d8a2-acf9-abe17a3aaed9, HandlerErrorCode: GeneralServiceException)
new BaseService (/private/var/folders/qr/ztb40vmn2pncyh8jpsgfnrt40000gp/T/jsii-kernel-4PEWmj/node_modules/aws-cdk-lib/aws-ecs/lib/base/base-service.js:1:3583)
\_ new FargateService (/private/var/folders/qr/ztb40vmn2pncyh8jpsgfnrt40000gp/T/jsii-kernel-4PEWmj/node_modules/aws-cdk-lib/aws-ecs/lib/fargate/fargate-service.js:1:967)
\_ new ApplicationLoadBalancedFargateService (/private/var/folders/qr/ztb40vmn2pncyh8jpsgfnrt40000gp/T/jsii-kernel-4PEWmj/node_modules/aws-cdk-lib/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.js:1:2300)
\_ Kernel._create (/private/var/folders/qr/ztb40vmn2pncyh8jpsgfnrt40000gp/T/tmpqkmckdm2/lib/program.js:9964:29)
\_ Kernel.create (/private/var/folders/qr/ztb40vmn2pncyh8jpsgfnrt40000gp/T/tmpqkmckdm2/lib/program.js:9693:29)
\_ KernelHost.processRequest (/private/var/folders/qr/ztb40vmn2pncyh8jpsgfnrt40000gp/T/tmpqkmckdm2/lib/program.js:11544:36)
\_ KernelHost.run (/private/var/folders/qr/ztb40vmn2pncyh8jpsgfnrt40000gp/T/tmpqkmckdm2/lib/program.js:11504:22)
\_ Immediate._onImmediate (/private/var/folders/qr/ztb40vmn2pncyh8jpsgfnrt40000gp/T/tmpqkmckdm2/lib/program.js:11505:46)
\_ processImmediate (node:internal/timers:464:21)
```


## Testing

### Static Analysis
Expand All @@ -67,81 +67,3 @@ Tests are available in the tests folder. Execute the following to run tests:
```
python -m pytest tests/ -s -v
```

## Secrets

We use the [AWS Secrets Manager](https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html)
to store secrets for this project. An AWS best practice is to create secrets
with a unique ID to prevent conflicts when multiple instances of this project
is deployed to the same AWS account. Our naming convention is
`<cfn stack name>/<environment id>/<secret name>`. The template in this repo' uses
the secret name, `ecs`, so an example Secrets Manager name is `myapp-dev-DockerFargateStack/dev/ecs`.


## Deployment from GitHub to AWS

To allow the GitHub action in your repository to deploy to AWS, submit a
PR like [this](https://github.com/Sage-Bionetworks-IT/organizations-infra/pull/771/files),
customizing the `StackName`, `Repositories` (name), and the `Account`(s) to
in which the infrastructure will be deployed. More on GitHub OIDC integration
[here](https://github.com/Sage-Bionetworks-IT/organizations-infra/tree/master/org-formation/650-identity-providers).

Once the PR is merged, an IAM role will be created in each AWS account listed in the PR.
Put the ARNs for the roles in the `ROLE_TO_ASSUME` field of `main.yml`, which
allows switching between development and production based on the git branch.

## VPC CIDR

Select a unique IP address range for the VPC (CIDR) following the instructions
[here](https://sagebionetworks.jira.com/wiki/spaces/IT/pages/2850586648/Setup+AWS+VPC).
Enter the range as the `VPC_CIDR` parameter in the file `cdk.json`.


## DNS and Certificates

Obtain the ARN of the ACM Certificate by following the instructions
[here](https://sagebionetworks.jira.com/wiki/spaces/IT/pages/2859302913/Admin+Tasks+for+CDK+Applications)
Set the value of `ACM_CERT_ARN` context variable to the ARN obtained above.

Finally, a DNS CNAME must be created in org-formation after the initial
deployment of the application to make the application available at the desired
URL. The CDK application exports the DNS name of the Application Load Balancer
to be consumed in org-formation. [An example PR setting up a CNAME](https://github.com/Sage-Bionetworks-IT/organizations-infra/pull/739).

Find the `LoadBalancerDNS` name: Navigate to the deployed stack in the AWS CloudFormation
console and click on the `Outputs` tab. On the row whose key is `LoadBalancerDNS` look for
the value in the `ExportName` column, e.g., `dca-dev-DockerFargateStack-LoadBalancerDNS`.
Now use the name in the `TargetHostName` definition, for example:

```
TargetHostName: !CopyValue [!Sub 'dca-dev-DockerFargateStack-LoadBalancerDNS', !Ref DnTDevAccount]
```

(You would also replace `DnTDevAccount` with the name of the account in which the application is deployed.)


## Debugging

Generally CDK deployments will create cloudformation events during a CDK deploy.
The events can be viewed in the AWS console under the cloudformation service page.
Viewing those events will help with errors during a deployment. Below are cases
where it might be difficult to debug due to misleading or insufficient error
messages from AWS

### Missing Secrets

Each new environment (dev/staging/prod/etc..) may require adding secrets. If a
secret is not created for the environment you may get an error with the following
stack trace..
```
Resource handler returned message: "Error occurred during operation 'ECS Deployment Circuit Breaker was triggered'." (RequestToken: d180e115-ba94-d8a2-acf9-abe17a3aaed9, HandlerErrorCode: GeneralServiceException)
new BaseService (/private/var/folders/qr/ztb40vmn2pncyh8jpsgfnrt40000gp/T/jsii-kernel-4PEWmj/node_modules/aws-cdk-lib/aws-ecs/lib/base/base-service.js:1:3583)
\_ new FargateService (/private/var/folders/qr/ztb40vmn2pncyh8jpsgfnrt40000gp/T/jsii-kernel-4PEWmj/node_modules/aws-cdk-lib/aws-ecs/lib/fargate/fargate-service.js:1:967)
\_ new ApplicationLoadBalancedFargateService (/private/var/folders/qr/ztb40vmn2pncyh8jpsgfnrt40000gp/T/jsii-kernel-4PEWmj/node_modules/aws-cdk-lib/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.js:1:2300)
\_ Kernel._create (/private/var/folders/qr/ztb40vmn2pncyh8jpsgfnrt40000gp/T/tmpqkmckdm2/lib/program.js:9964:29)
\_ Kernel.create (/private/var/folders/qr/ztb40vmn2pncyh8jpsgfnrt40000gp/T/tmpqkmckdm2/lib/program.js:9693:29)
\_ KernelHost.processRequest (/private/var/folders/qr/ztb40vmn2pncyh8jpsgfnrt40000gp/T/tmpqkmckdm2/lib/program.js:11544:36)
\_ KernelHost.run (/private/var/folders/qr/ztb40vmn2pncyh8jpsgfnrt40000gp/T/tmpqkmckdm2/lib/program.js:11504:22)
\_ Immediate._onImmediate (/private/var/folders/qr/ztb40vmn2pncyh8jpsgfnrt40000gp/T/tmpqkmckdm2/lib/program.js:11505:46)
\_ processImmediate (node:internal/timers:464:21)
```
30 changes: 12 additions & 18 deletions cdk.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,36 +31,30 @@
"aws-cn"
],
"dev": {
"IMAGE_PATH_AND_TAG": "ghcr.io/sage-bionetworks/myapp:release-1.0",
"AWS_DEFAULT_REGION": "us-east-1",
"PORT": "3838",
"PORT": "443",
"CONTAINER_ENV": {
"name1": "value1",
"name2": "value2"
},
"TAGS": {
"CostCenter": "NO PROGRAM / 000000",
"OwnerEmail": "joe@sagebase.org"
"CostCenter": "Platform Infrastructure / 990300",
"OwnerEmail": "synapseeng@sagebase.org"
},
"STACK_NAME_PREFIX": "myapp-dev",
"VPC_CIDR": "172.31.0.0/24",
"ACM_CERT_ARN": "arn:aws:acm:us-east-1:<DEV_ACCOUNT_ID>:certificate/<DEV_CERT_ID>"
"STACK_NAME_PREFIX": "registry-dev",
"VPC_CIDR": "172.29.0.0/24",
"ACM_CERT_ARN": "arn:aws:acm:us-east-1:449435941126:certificate/bbd59a26-ad30-4b74-ad2d-194241801b22"
},
"prod": {
"IMAGE_PATH_AND_TAG": "ghcr.io/sage-bionetworks/myapp:release-1.0",
"AWS_DEFAULT_REGION": "us-east-1",
"PORT": "3838",
"PORT": "443",
"CONTAINER_ENV": {
"name1": "value1",
"name2": "value2"
},
"TAGS": {
"CostCenter": "NO PROGRAM / 000000",
"OwnerEmail": "joe@sagebase.org"
"CostCenter": "Platform Infrastructure / 990300",
"OwnerEmail": "synapseeng@sagebase.org"
},
"STACK_NAME_PREFIX": "myapp-prod",
"VPC_CIDR": "172.32.0.0/24",
"ACM_CERT_ARN": "arn:aws:acm:us-east-1:<PROD_ACCOUNT_ID>:certificate/<PROD_CERT_ID>"
"STACK_NAME_PREFIX": "registry-prod",
"VPC_CIDR": "172.29.1.0/24",
"ACM_CERT_ARN": "arn:aws:acm:us-east-1:325565585839:certificate/7c42c355-3d69-4537-a5e6-428212db646f"
}
}
}
2 changes: 1 addition & 1 deletion config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

CONTEXT_ENVS = ["dev", "staging", "prod"]
CONTEXT_ENVS = ["dev", "prod"]
TAGS_CONTEXT = "TAGS"
STACK_NAME_PREFIX_CONTEXT = "STACK_NAME_PREFIX"
Loading

0 comments on commit adb2fdc

Please sign in to comment.