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

PLFM-8554: Run Synapse Docker registry using ECS #1

Merged
merged 37 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
99758da
PLFM-8554: initial commit
brucehoff Aug 29, 2024
9dcafbc
PLFM-8554: Run Synapse Docker registry using ECS
brucehoff Sep 4, 2024
3e1938e
PLFM-8554: Fixed whitespace
brucehoff Sep 4, 2024
302b6f6
PLFM-8554: Typo
brucehoff Sep 4, 2024
4cf6a2c
PLFM-8554: Typo
brucehoff Sep 4, 2024
60ecf7e
PLFM-8554: build container with Synapse configuration baked-in
brucehoff Sep 19, 2024
d33a6ce
PLFM-8554: fixed bugs in action, python, and whitespace
brucehoff Sep 19, 2024
57d556b
PLFM-8554: added checkout to docker build job
brucehoff Sep 20, 2024
9661961
PLFM-8554: added Docer linting and fixed Dockerfile
brucehoff Sep 20, 2024
dbf6212
PLFM-8554: removed dash from env var name; chnaged CMD to ENTRYPOINT …
brucehoff Sep 20, 2024
e980b60
PLFM-8554: changed bash to sh in startup.sh
brucehoff Sep 20, 2024
3eecee4
PLFM-8554: Updated CIDR range; updated GitHub workflow
brucehoff Sep 20, 2024
f7344dc
PLFM-8554: updated GitHub workflow
brucehoff Sep 20, 2024
1c4abc2
PLFM-8554: fixed 'if' in workflow
brucehoff Sep 20, 2024
539aa60
PLFM-8554: fixed references to env.branch in workflow
brucehoff Sep 20, 2024
a5346ec
PLFM-8554: -> .branch
brucehoff Sep 20, 2024
91e1f21
PLFM-8554: env.branch
brucehoff Sep 20, 2024
64c4528
PLFM-8554: env.branch
brucehoff Sep 20, 2024
a2d5da2
PLFM-8554: env.branch
brucehoff Sep 20, 2024
9e97169
PLFM-8554: don't refer to env var in if statement
brucehoff Sep 21, 2024
a141f6e
PLFM-8554: don't refer to env var anywhere
brucehoff Sep 21, 2024
c7bd34d
PLFM-8554: only publish container or deploy cdk on push/merge, not PR
brucehoff Sep 21, 2024
4b8c670
PLFM-8554: only publish container or deploy cdk on push/merge, not PR
brucehoff Sep 21, 2024
f76d001
PLFM-8554: typo
brucehoff Sep 21, 2024
c93a7d9
PLFM-8554: order of operations
brucehoff Sep 21, 2024
4bfc3e0
PLFM-8554: syntax
brucehoff Sep 21, 2024
3c31a40
PLFM-8554: rotated dev token signing key/cert
brucehoff Sep 23, 2024
efc0d46
PLFM-8554: updates from CR
brucehoff Sep 25, 2024
f2d0bd9
PLFM-8554: updates from CR
brucehoff Sep 25, 2024
a139b19
PLFM-8554: updates from CR
brucehoff Sep 25, 2024
e2d7c51
PLFM-8554: added no-publish Docker build to workflow
brucehoff Sep 25, 2024
609e952
PLFM-8554: added no-publish Docker build to workflow
brucehoff Sep 25, 2024
2e59e94
PLFM-8554: added no-publish Docker build to workflow
brucehoff Sep 25, 2024
5979d83
PLFM-8554: Build registry Docker image on-the-fly
brucehoff Oct 17, 2024
2b647aa
PLFM-8554: white space
brucehoff Oct 17, 2024
69dd8ee
PLFM-8554: add openssl to requirements.txt
brucehoff Oct 17, 2024
2520b20
PLFM-8554: Updated CIDR ranges not to be conflicting
brucehoff Oct 17, 2024
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
8 changes: 8 additions & 0 deletions .dockerfilelintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
# https://github.com/replicatedhq/dockerfilelint
rules:
apt-get-upgrade: off
apt-get-update_require_install: off
apt-get_recommends: off
apt-get_missing_rm: off
sudo_usage: off
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@v1
brucehoff marked this conversation as resolved.
Show resolved Hide resolved
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
67 changes: 52 additions & 15 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,32 +25,69 @@ 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:
brucehoff marked this conversation as resolved.
Show resolved Hide resolved
needs: tests
strategy:
matrix:
stack: ['dev', 'prod']
uses: "./.github/workflows/synth-test.yml"
with:
CONTEXT: dev
CONTEXT: ${{ matrix.stack }}

synth-test-prod:
needs: tests
uses: "./.github/workflows/synth-test.yml"
with:
CONTEXT: prod
build-registry-container:
brucehoff marked this conversation as resolved.
Show resolved Hide resolved
needs: [tests, synth-test]
# only run on a push/merge, not on a PR
if: ${{ github.ref_name == 'dev' || github.ref_name == 'prod' }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/[email protected]
with:
images: ghcr.io/${{ github.repository }}

- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: create self-signed cert
run: >
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"

- name: Build and push Docker image
uses: docker/[email protected]
with:
context: .
build-args: stack=${{ github.ref_name }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

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, build-registry-container]
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
brucehoff marked this conversation as resolved.
Show resolved Hide resolved

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, build-registry-container]
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 .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/pryorda/dockerfilelint-precommit-hooks
brucehoff marked this conversation as resolved.
Show resolved Hide resolved
rev: v0.1.0
hooks:
- id: dockerfilelint
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'
brucehoff marked this conversation as resolved.
Show resolved Hide resolved
# 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
brucehoff marked this conversation as resolved.
Show resolved Hide resolved
brucehoff marked this conversation as resolved.
Show resolved Hide resolved

COPY startup.sh /startup.sh

ENTRYPOINT ["/startup.sh"]
143 changes: 26 additions & 117 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,55 +1,42 @@
# 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.

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.

To manually create a virtualenv on MacOS and Linux:

```
$ python3 -m venv .venv
```
We use the [AWS Secrets Manager](https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html)

After the init process completes and the virtualenv is created, you can use the following
step to activate your virtualenv.
In dev the secret is named `registry-dev-DockerFargateStack/dev/ecs` and in the prod stack,
`registry-prod-DockerFargateStack/prod/ecs`

```
$ source .venv/bin/activate
```
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.

If you are a Windows platform, you would activate the virtualenv like this:
### 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, published to GitHub as `ghcr.io/synapse-docker-registry`, making two versions under
two different tags, `dev` and `prod`, each with its own configuration. Note that the configuration files we add contain no secrets. The only required secret (the event listener
authorization credential) is inserted into the registry configuration file at the time the container is started up.

brucehoff marked this conversation as resolved.
Show resolved Hide resolved
```
% .venv\Scripts\activate.bat
```
### Missing Secrets

Once the virtualenv is activated, you can install the required dependencies.
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:

```
$ pip install -r requirements.txt
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)
```

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.

```
$ cdk synth --context env=dev
```

## Testing

Expand All @@ -67,81 +54,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)
```
32 changes: 14 additions & 18 deletions cdk.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,36 +31,32 @@
"aws-cn"
],
"dev": {
"IMAGE_PATH_AND_TAG": "ghcr.io/sage-bionetworks/myapp:release-1.0",
"IMAGE_PATH_AND_TAG": "ghcr.io/sage-bionetworks/synapse-docker-registry:dev",
brucehoff marked this conversation as resolved.
Show resolved Hide resolved
"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.30.0.0/24",
brucehoff marked this conversation as resolved.
Show resolved Hide resolved
"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",
"IMAGE_PATH_AND_TAG": "ghcr.io/sage-bionetworks/synapse-docker-registry:prod",
"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.30.1.0/24",
brucehoff marked this conversation as resolved.
Show resolved Hide resolved
"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
Loading