├── .github └── PULL_REQUEST_TEMPLATE.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── examples ├── index.html └── nginx.cloudformation.yaml ├── images ├── architecture.png ├── containers.png ├── homepage.png └── logs.png └── src ├── Dockerfile └── entrypoint.sh /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, 6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 10 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 11 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 12 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 13 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 14 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Amazon ECS Config Maps Example 2 | 3 | When running a containerized workload it is best practice to remove all 4 | environment specific code, such as database passwords or API URLs, from your 5 | container image. This ensures the workload is 1. portable and 2. does not 6 | contain any sensitive data. It is then recommended to either use [Environment 7 | Variables](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/taskdef-envfiles.html) 8 | or 9 | [Secrets](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data.html) 10 | to pass back in the environment specific data back at runtime. 11 | 12 | Many workloads also contain configuration files which are also environment 13 | specific. Following the guidance above, we want to remove these environment 14 | specific configuration files from our container image and pass them back in at 15 | runtime. Depending on your container orchestrator, it may have a first class 16 | configuration file resource such as Kubernetes 17 | [ConfigMaps](https://kubernetes.io/docs/concepts/configuration/configmap/) or 18 | Docker Swarm [Configs](https://docs.docker.com/engine/swarm/configs/). Amazon 19 | Elastic Container Service (Amazon ECS) does not have a native configuration file 20 | resource. 21 | 22 | Instead there are multiple ways to pass a configuration file into an Amazon ECS 23 | Task, such as Amazon EFS or SSM Parameter Store. In in this project we show an 24 | alternative approach leveraging Amazon S3 and an init container. This example is 25 | supported on both Amazon ECS running on Amazon EC2 as well as Amazon ECS running 26 | on AWS Fargate. 27 | 28 | The architecture for this solution is as follows: 29 | 30 | 1. A user uploads a configuration file to an Amazon S3 bucket 31 | 2. A user creates a Amazon ECS Task Definition with 2 containers and 1 shared 32 | local volume. 33 | 1. The first container is an "init" container. It will download the 34 | configuration file from S3 to the shared local volume. The first 35 | container will then exit successfully. 36 | 2. The second container is the workload that requires the configuration 37 | file. The workload container will not start until the first container has 38 | exited successfully, leveraging Amazon ECS’s Depends On, and will load 39 | its configuration from the shared local volume. 40 | 41 | !["Architecture"](images/architecture.png) 42 | 43 | ## init container 44 | 45 | The init container is built on top of the official 46 | [aws-cli](https://hub.docker.com/r/amazon/aws-cli) container image, and contains 47 | a single bash script that will run aws s3 cp to download each configuration 48 | file. This image could have been built with an AWS SDK instead of the aws cli, 49 | but with a design goal of keeping this simple, light weight and easy to 50 | maintain, going down this path removes a lot of the dependency management. 51 | 52 | The init container is configured with 3 environment variables for each 53 | configuration file: 54 | 55 | * `S3_BUCKET_FILE_*` - The S3 Bucket that contains the configuration file. 56 | * `SRC_FILE_PATH_FILE_*` - The path within the S3 bucket to locate the 57 | configuration file `/prodfiles/nginx.conf`. 58 | * `DEST_FILE_PATH_FILE_* `- The path within the init container you want to write 59 | the configuration file too. For example if the shared local volume is mounted 60 | to `/data` then this value could be `/data/nginx.conf`. 61 | 62 | This same init container can be used for multiple configuration files, the 63 | wildcard should be replaced with an integer for each configuration file you want 64 | to use. For example `S3_BUCKET_FILE_1` , `SRC_FILE_PATH_FILE_1` , 65 | `DEST_FILE_PATH_FILE_1` for the first configuration file and then 66 | `S3_BUCKET_FILE_2` , `SRC_FILE_PATH_FILE_2` , `DEST_FILE_PATH_FILE_2` for the 67 | second configuration file. By default the init container supports up to 20 68 | configuration files. 69 | 70 | ### Building the init container: 71 | 72 | ```bash 73 | $ git clone https://github.com/aws-samples/amazon-ecs-configmaps-example.git 74 | $ cd amazon-ecs-configmaps-example/src/ 75 | 76 | # Build the Container Image 77 | $ export DOCKER_BUILDKIT=1 78 | $ export IMAGE_URI=.dkr.ecr..amazonaws.com/ecs-configmap 79 | $ export IMAGE_TAG=v0.1 80 | 81 | $ docker build \ 82 | --tag $IMAGE_URI:$IMAGE_TAG \ 83 | . 84 | 85 | # Push the Container Image to the Image Repository 86 | $ docker push $IMAGE_URI:$IMAGE_TAG 87 | ``` 88 | 89 | ### Example 90 | 91 | The examples repository contains a simple nginx example. The directory 92 | contains a cloudformation template with a Task Definition, as well as an 93 | `index.html` file. In the walk through we will deploy an nginx container to ECS 94 | using the upstream nginx image, we will also deploy a second container in the 95 | ECS Task, the init container. The init container will download the `index.html` 96 | from Amazon S3 and place it in the nginx container at runtime. 97 | 98 | > Remember this index.html file could be any static configuration file that you 99 | > want to pass into a workload at runtime. The example is simplistic by design, 100 | > a more common example would be a Fluent bit configuration file. 101 | 102 | When running the below the commands, ensure you have the container image and tag 103 | Exported in your shell from the previous step. These instructions also assume 104 | you have an existing VPC, Subnet, Security Group and ECS Cluster in your AWS 105 | account. It's worth nothing these instructions are not deploying a highly 106 | available, fault tolerant web server; it is simply highlighting the 107 | functionality of the init container. 108 | 109 | ```bash 110 | # Create the Task Definition 111 | $ cd ../ 112 | $ aws cloudformation create-stack \ 113 | --stack-name ecs-configmaps \ 114 | --template-body file://examples/nginx.cloudformation.yaml \ 115 | --capabilities CAPABILITY_IAM \ 116 | --parameters \ 117 | ParameterKey=InitImage,ParameterValue="${IMAGE_URI}:${IMAGE_TAG}" 118 | 119 | # Ensure to replace the S3 Bucket created by the Cloudformation Template 120 | $ S3_BUCKET_NAME=$(aws cloudformation describe-stacks --stack-name ecs-configmaps --query "Stacks[0].Outputs[?OutputKey=='S3Bucket'].OutputValue" --output text) 121 | $ aws s3 cp \ 122 | examples/index.html \ 123 | s3://$S3_BUCKET_NAME/ 124 | 125 | # To then run the task you need to pass back in an existing resources. 126 | $ export SECURITY_GROUP_ID= 127 | $ export SUBNET_ID= 128 | $ export ECS_CLUSTER= 129 | 130 | $ aws ecs run-task \ 131 | --cluster $ECS_CLUSTER \ 132 | --task-definition ecs-configmaps-taskdef \ 133 | --launch-type="FARGATE" \ 134 | --network-configuration '{ "awsvpcConfiguration": { "assignPublicIp":"ENABLED", "securityGroups": ["'$SECURITY_GROUP_ID'"], "subnets": ["'$SUBNET_ID'"]}}' 135 | ``` 136 | 137 | To verify the init container worked successfully, log into the AWS Console and 138 | browse to the ECS Console. Within your ECS Cluster, select Tasks, and select the 139 | ecs-configmap task you just deployed. 140 | 141 | Here you should see that the nginx container is running and the init container 142 | has STOPPED. This is working as designed because once the init container has 143 | downloaded the configuration files, it stops and the nginx container starts. 144 | 145 | !["Architecture"](images/containers.png) 146 | 147 | If you browse to the logs of the init container, you should see that it 148 | successfully downloaded the configuration file from S3 and stored it in the 149 | shared local volume. 150 | 151 | !["Architecture"](images/logs.png) 152 | 153 | Finally, depending on the networking configuration of your VPC, if you have used 154 | a public subnet when deploying the Task, with a Rule on the Security Group 155 | allowing access to port 80 from your IP, you should be able to browse to the 156 | nginx Task and see your static content. 157 | 158 | !["Architecture"](images/homepage.png) 159 | 160 | 161 | ## License 162 | 163 | This library is licensed under the MIT-0 License. 164 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello Amazon ECS Fans! -------------------------------------------------------------------------------- /examples/nginx.cloudformation.yaml: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | AWSTemplateFormatVersion: '2010-09-09' 5 | Description: Nginx Init Example 6 | 7 | Parameters: 8 | InitImage: 9 | Description: Init Container Image 10 | Type: 'String' 11 | 12 | Resources: 13 | ################### 14 | ## S3 Resources ### 15 | ################### 16 | S3Bucket: 17 | Type: AWS::S3::Bucket 18 | 19 | ################### 20 | ## CW Resources ### 21 | ################### 22 | LogGroup: 23 | Type: AWS::Logs::LogGroup 24 | Properties: 25 | LogGroupName: !Join ['-', [!Sub '${AWS::StackName}', 'loggroup']] 26 | RetentionInDays: 7 27 | 28 | ################### 29 | ## IAM Resources ## 30 | ################### 31 | EcsTaskExecutionRole: 32 | Type: AWS::IAM::Role 33 | Properties: 34 | AssumeRolePolicyDocument: 35 | Statement: 36 | - Effect: Allow 37 | Principal: 38 | Service: [ecs-tasks.amazonaws.com] 39 | Action: ['sts:AssumeRole'] 40 | Path: / 41 | ManagedPolicyArns: 42 | - 'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy' 43 | 44 | EcsTaskRole: 45 | Type: AWS::IAM::Role 46 | Properties: 47 | AssumeRolePolicyDocument: 48 | Statement: 49 | - Effect: Allow 50 | Principal: 51 | Service: [ecs-tasks.amazonaws.com] 52 | Action: ['sts:AssumeRole'] 53 | Path: / 54 | 55 | S3Policies: 56 | Type: 'AWS::IAM::Policy' 57 | Properties: 58 | PolicyName: 's3access' 59 | PolicyDocument: 60 | Version: '2012-10-17' 61 | Statement: 62 | - Effect: 'Allow' 63 | Action: 64 | - 's3:GetObject' 65 | - 's3:ListBucket' 66 | Resource: 67 | - !Sub 'arn:${AWS::Partition}:s3:::${S3Bucket}' 68 | - !Sub 'arn:${AWS::Partition}:s3:::${S3Bucket}/*' 69 | Roles: 70 | - Ref: 'EcsTaskRole' 71 | 72 | ################### 73 | ## ECS Resources ## 74 | ################### 75 | TaskDefinition: 76 | Type: AWS::ECS::TaskDefinition 77 | Properties: 78 | Family: !Join ['-', [!Sub '${AWS::StackName}', 'taskdef']] 79 | Cpu: '256' 80 | Memory: '512' 81 | NetworkMode: awsvpc 82 | RequiresCompatibilities: 83 | - FARGATE 84 | ExecutionRoleArn: !GetAtt EcsTaskExecutionRole.Arn 85 | TaskRoleArn: !GetAtt EcsTaskRole.Arn 86 | ContainerDefinitions: 87 | - Name: 'init' 88 | Image: !Ref InitImage 89 | Essential: false 90 | Environment: 91 | - Name: 'S3_BUCKET_FILE_1' 92 | Value: !Ref S3Bucket 93 | - Name: 'SRC_FILE_PATH_FILE_1' 94 | Value: 'index.html' 95 | - Name: 'DEST_FILE_PATH_FILE_1' 96 | Value: '/data/index.html' 97 | MountPoints: 98 | - ContainerPath: '/data' 99 | SourceVolume: 'staticontent' 100 | LogConfiguration: 101 | LogDriver: 'awslogs' 102 | Options: 103 | awslogs-group: !Ref LogGroup 104 | awslogs-region: !Ref 'AWS::Region' 105 | awslogs-stream-prefix: 'init' 106 | - Name: 'nginx' 107 | Image: public.ecr.aws/docker/library/nginx:latest 108 | DependsOn: 109 | - ContainerName: init 110 | Condition: SUCCESS 111 | Essential: true 112 | PortMappings: 113 | - ContainerPort: 80 114 | Protocol: 'TCP' 115 | MountPoints: 116 | - ContainerPath: '/usr/share/nginx/html' 117 | ReadOnly: true 118 | SourceVolume: 'staticontent' 119 | LogConfiguration: 120 | LogDriver: 'awslogs' 121 | Options: 122 | awslogs-group: !Ref LogGroup 123 | awslogs-region: !Ref 'AWS::Region' 124 | awslogs-stream-prefix: 'nginx' 125 | 126 | Volumes: 127 | - Name: 'staticontent' 128 | 129 | Outputs: 130 | S3Bucket: 131 | Description: S3 Bucket Name 132 | Value: !Ref S3Bucket 133 | -------------------------------------------------------------------------------- /images/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ecs-configmaps-example/4d899d817007a3e7dc9fc209d80202dfb785a84c/images/architecture.png -------------------------------------------------------------------------------- /images/containers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ecs-configmaps-example/4d899d817007a3e7dc9fc209d80202dfb785a84c/images/containers.png -------------------------------------------------------------------------------- /images/homepage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ecs-configmaps-example/4d899d817007a3e7dc9fc209d80202dfb785a84c/images/homepage.png -------------------------------------------------------------------------------- /images/logs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ecs-configmaps-example/4d899d817007a3e7dc9fc209d80202dfb785a84c/images/logs.png -------------------------------------------------------------------------------- /src/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1.4 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: MIT-0 4 | 5 | FROM public.ecr.aws/aws-cli/aws-cli:latest 6 | 7 | # AWS AppMesh does not send traffic through the envoy proxy if the process is 8 | # running as user 1337, hence why we setup that demouser in this RUN command. 9 | RUN <> /etc/passwd 14 | echo -e "\ndemouser:*:15455:0:99999:7:::" >> /etc/shadow 15 | echo "demouser ALL=(ALL:ALL) NOPASSWD:ALL" >> /etc/sudoers 16 | mkdir /home/demouser 17 | chown 1337:1337 /home/demouser 18 | EOF 19 | 20 | COPY entrypoint.sh /entrypoint.sh 21 | 22 | USER 1337 23 | ENTRYPOINT ["/bin/sh", "-c", "/entrypoint.sh"] -------------------------------------------------------------------------------- /src/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | for i in {1..20} # Max 20 Files 6 | do 7 | # Retrieve Bucket Name 8 | BUCKET_NAME="S3_BUCKET_FILE_"$i 9 | if [[ -z "${!BUCKET_NAME}" ]]; then 10 | echo "$BUCKET_NAME environment variable is not set" 11 | exit 0 12 | fi 13 | echo "$BUCKET_NAME has been set as ${!BUCKET_NAME}" 14 | 15 | # Retrieve File Location in Bucket 16 | SRC_FILE_PATH="SRC_FILE_PATH_FILE_"$i 17 | if [ -z "${!SRC_FILE_PATH}" ]; then 18 | echo "$SRC_FILE_PATH environment variable is not set" 19 | exit 0 20 | fi 21 | echo "$SRC_FILE_PATH has been set as ${!SRC_FILE_PATH}" 22 | 23 | # Retrieve Target File Location in Container 24 | DEST_FILE_PATH="DEST_FILE_PATH_FILE_"$i 25 | if [ -z "${!DEST_FILE_PATH}" ]; then 26 | echo "$DEST_FILE_PATH environment variable is not set" 27 | exit 0 28 | fi 29 | echo "$DEST_FILE_PATH has been set as ${!DEST_FILE_PATH}" 30 | 31 | # Remove any directories from the path 32 | FILE_NAME=$(echo "${!SRC_FILE_PATH}" | xargs basename) 33 | echo "${FILE_NAME}" 34 | 35 | # Download the file from s3 to the local directory 36 | aws s3 cp \ 37 | s3://"${!BUCKET_NAME}"/"${!SRC_FILE_PATH}" \ 38 | "${HOME}"/"${FILE_NAME}" 39 | 40 | # Copy the file to a mounted directory. The assumption is the mounted 41 | # directory is a bind mount, therefore sudo privileges are required. 42 | sudo cp "${HOME}"/"${FILE_NAME}" "${!DEST_FILE_PATH}" 43 | done --------------------------------------------------------------------------------