├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── archive-eks-alb-bg.tar.gz
├── cdk
├── .DS_Store
├── .gitignore
├── README.md
├── bin
│ ├── .DS_Store
│ └── cdk.ts
├── cdk.json
├── lib
│ ├── .DS_Store
│ └── cdk-stack.ts
├── package.json
├── test
│ ├── .DS_Store
│ └── cdk.test.ts
└── tsconfig.json
├── cicd
├── Jenkinsfile
├── jenkins-ecs-workshop.json
├── jenkins-ecs-workshop2.json
├── jenkins-ecs-workshop3.json
├── jenkins-jobs-archive.tar.gz
└── jenkins-jobs-archive3.tar.gz
├── dockerAssets.d
├── Dockerfile
└── entrypoint.sh
├── flask-docker-app
├── .DS_Store
├── Dockerfile
├── app.py
├── k8s
│ ├── .DS_Store
│ ├── alb-ingress-controller.yaml
│ ├── flask-ALB-namespace.yaml
│ ├── flask.yaml
│ ├── flaskALBBlue.yaml
│ ├── flaskALBGreen.yaml
│ ├── flaskALBIngress_query.yaml
│ ├── flaskALBIngress_query2.yaml
│ ├── flaskBlue.yaml
│ ├── flaskGreen.yaml
│ ├── setup.sh
│ └── setup2.sh
├── pyvenv.cfg
├── requirements.txt
├── static
│ └── css
│ │ ├── bootstrap-responsive.min.css
│ │ └── bootstrap.min.css
└── templates
│ └── hello.html
└── images
├── alb-dns.png
├── alb-tg-check1.png
├── alb-tg-check2.png
├── canary-lb.png
├── cfn-kubectl.png
├── eks-bg-1.png
├── eks-bg-2.png
├── eks-canary.png
├── eks-cicd-codebuild.png
├── flask01.png
├── flask02.png
├── stage12-green.png
├── stage34-green.png
├── web-blue-inv.png
├── web-blue.png
├── web-default.png
├── web-green-inv.png
└── web-green.png
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
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 *master* 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 |
61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes.
62 |
--------------------------------------------------------------------------------
/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
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to 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
10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 |
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Building CI/CD with Blue/Green and Canary Deployments on EKS using CDK
2 |
3 | In this workshop you'll learn building a CI/CD pipeline (AWS CodePipeline) to develop a web-based application, containerize it, and deploy it on a Amazon EKS cluster. You'll use the blue/green method to deploy application and review the switchover using Application Load Balancer (ALB) Target-groups. You will spawn this infrastructure using AWS Cloud Development Kit (CDK), enabling you to reproduce the environment when needed, in relatively fewer lines of code.
4 |
5 | The hosting infrastructure consists of pods hosted on Blue and Green service on Kubernetes Worker Nodes, being accessed via an Application LoadBalancer. The Blue service represents the production environment accessed using the ALB DNS with http query (group=blue) whereas Green service represents a pre-production / test environment that is accessed using a different http query (group=green). The CodePipeline build stage uses CodeBuild to dockerize the application and post the images to Amazon ECR. In subsequent stages, the image is picked up and deployed on the Green service of the EKS. The Codepipeline workflow is then blocked at the approval stage, allowing the application in Green service to be tested. Once the application is confirmed to be working fine, the user can issue an approval at the Approval Stage and the application is then deployed on to the Blue Service.
6 |
7 | The Blue/Green architecture diagrams are provided below:
8 |
9 |
10 |
11 |
12 |
13 | The CodePipeline would look like the below figure:
14 |
15 |
16 |
17 |
18 | The current workshop is based upon this link and the CDK here is extended further to incorporate CodePipeline, Blue/Green Deployment on EKS with ALB. We will also use the weighted target-group to configure B/G Canary Deployment method. Note that currently CodeDeploy does not support deploying on EKS and thus we will instead use CodeBuild to run commands to deploy the Containers on Pods, spawn the EKS Ingress controller and Ingress resource that takes form of ALB. This workshop focuses on providing a simplistic method, though typical deployable model for production environments. Note that blue/green deployments can be achieved using AppMesh, Lambda, DNS based canary deployments too.
19 |
20 | ### Procedure to follow:
21 |
22 | Step1. Cloud9 and commands to run:
23 |
24 | First launch a Cloud9 terminal and prepare it with following commands:
25 |
26 | ```bash
27 | sudo yum install -y jq
28 | export ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
29 | export AWS_REGION=$(curl -s 169.254.169.254/latest/dynamic/instance-identity/document | jq -r '.region')
30 | echo "export ACCOUNT_ID=${ACCOUNT_ID}" | tee -a ~/.bash_profile
31 | echo "export AWS_REGION=${AWS_REGION}" | tee -a ~/.bash_profile
32 | aws configure set default.region ${AWS_REGION}
33 | aws configure get default.region
34 | ```
35 | Ensure the Cloud9 is assigned a role of an administrator and from Cloud9 -> AWS Settings -> Credentials -> Disable the Temporary Credentials
36 | Now install kubectl package:
37 |
38 | ```bash
39 | curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.17.0/bin/linux/amd64/kubectl
40 | chmod +x ./kubectl
41 | sudo mv ./kubectl /usr/local/bin/kubectl
42 | kubectl help
43 | ```
44 | Prepare CDK prerequisite:
45 |
46 | ```bash
47 | sudo yum install -y npm
48 | npm install -g aws-cdk@1.124.0 --force
49 | npm install -g typescript@latest
50 | ```
51 | Run git clone on this repository from Cloud9:
52 |
53 | ```bash
54 | git clone https://github.com/aws-samples/amazon-eks-cdk-blue-green-cicd.git amazon-eks-cicd-codebuild-eks-alb-bg
55 | ```
56 |
57 | Once cloned, run the below commands:
58 | ```bash
59 | cd amazon-eks-cicd-codebuild-eks-alb-bg
60 | ```
61 |
62 | Note: For this workshop, we are using CDK version 1.30. If using the latest CDK version using "npm install -g aws-cdk" (without a version specification) then you would need to modify the EKS construct to include version number too.
63 |
64 |
65 | ```bash
66 | git init
67 | git add .
68 | git commit -m "Initial Commit"
69 | git status
70 | git log
71 | ```
72 | Now run the CDK steps as below:
73 |
74 | ```bash
75 | cd cdk
76 | cdk init
77 | npm install
78 | npm run build
79 | cdk ls
80 | ```
81 | Ensure the output is CdkStackEksALBBg
82 |
83 | ```bash
84 | cdk synth
85 | cdk bootstrap aws://$ACCOUNT_ID/$AWS_REGION
86 | cdk deploy
87 | ```
88 | You may be asked to confirm the creation of the roles and authorization before the CloudFormation is executed, for which, you can respond with a “Y”.
89 |
90 | The infrastructure will take some time to be created, please wait until you see the Output of CloudFormation printed on the terminal. Until then, take time to review the CDK code in the below file: cdk/lib/cdk-stack.ts
91 |
92 | You may also check and compare the CloudFormation Template created from this CDK stack:
93 | cdk/cdk.out/CdkStackEksALBBg.template.json
94 |
95 |
96 | Step2: Configure EKS environment:
97 |
98 | Once the cdk is deployed successfully, go to the CloudFormation Service, select the CdkStackALBEksBg stack and go to the outputs section to copy the value from the field "ClusterConfigCommand".
99 |
100 |
101 |
102 | Then paste this output into Cloud9 terminal to configure the EKS context.
103 | Ensure you can see 2 nodes listed in the output of :
104 | ```bash
105 | kubectl get nodes
106 | ```
107 |
108 | Now configure the EKS cluster with the deployment, service and ingress resource as ALB using following set of commands:
109 |
110 | ```bash
111 | cd ../flask-docker-app/k8s
112 | ls setup.sh
113 | chmod +x setup.sh
114 | chmod +x setup2.sh
115 | INSTANCE_ROLE=$(aws cloudformation describe-stack-resources --stack-name CdkStackALBEksBg | jq .StackResources[].PhysicalResourceId | grep CdkStackALBEksBg-ClusterDefaultCapacityInstanceRol | tr -d '["\r\n]')
116 | CLUSTER_NAME=$(aws cloudformation describe-stack-resources --stack-name CdkStackALBEksBg | jq '.StackResources[] | select(.ResourceType=="Custom::AWSCDK-EKS-Cluster").PhysicalResourceId' | tr -d '["\r\n]')
117 | echo "INSTANCE_ROLE = " $INSTANCE_ROLE
118 | echo "CLUSTER_NAME = " $CLUSTER_NAME
119 | ```
120 |
121 | Note: Before proceeding further, confirm to see that both the variables $INSTANCE_ROLE and $CLUSTER_NAME have values populated. IF not, please bring it to the attention of the workshop owner, possibly the IAM role naming convention may have changed with the version.
122 | Also, after EKS version 1.16 onwards, the k8 deploy API's using apps/v1beta1 is deprecated to apps/v1. The update has been made into the yaml files, however, if you are using an older version of EKS, you may need to modify this back.
123 |
124 | ```bash
125 | ./setup2.sh $AWS_REGION $INSTANCE_ROLE $CLUSTER_NAME
126 | ```
127 |
128 | Step3: Modify the ALB Security Group:
129 |
130 | Modify the Security Group (ControlPlaneSecurityGroup) for the newly spawned Application Load Balancer to add an incoming rule to allow http port 80 for the 0.0.0.0/0.
131 | Services -> EC2 -> Load Balancer -> Select the latest created ALB -> Click Description Tab -> Scroll down to locate the Security Group Edit this security group to add a new rule with following parameters: http, 80, 0.0.0.0/0
132 |
133 | Additionally, from EKS version 1.17 onwards, you would also need to change the security-group for Worker Nodes Data Plane (InstanceSecurityGroup) by adding an incoming rule to allow http port 80 for the ControlPlaneSecurityGroup (ALB).
134 |
135 | Now, check the newly created LoadBalancer and review the listener routing rules: Services -> EC2 -> Load Balancer -> Select the latest created ALB -> Click Listeners Tab -> View/Edit Rules You would see the below settings shown:
136 |
137 | Check the Load Balancer Target-groups and ensure the healthy hosts have registered and health check is consistently passing as shown below:
138 |
139 |
140 |
141 | Check the healthy hosts count graph to ensure the hosts, containers are stable:
142 |
143 |
144 |
145 | Step4: Upload the Application to CodeCommit repo:
146 |
147 | Now that the ALB is setup, complete the last part of the configuration by uploading the code to CodeCommit repository:
148 |
149 | ```bash
150 | cd ../..
151 | pwd => confirm your current directory is amazon-eks-cicd-codebuild-eks-alb-bg
152 | git add flask-docker-app/k8s/alb-ingress-controller.yaml
153 | git add flask-docker-app/k8s/flaskALBIngress_query.yaml
154 | git add flask-docker-app/k8s/flaskALBIngress_query2.yaml
155 | git add flask-docker-app/k8s/iam-policy.json
156 | git commit -m "Updated files"
157 | git remote add codecommit https://git-codecommit.$AWS_REGION.amazonaws.com/v1/repos/CdkStackALBEksBg-repo
158 | git push -u codecommit master
159 | ```
160 |
161 | This will push the last commit we carried out in our preparation section, which in turn will trigger the CodePipeline.
162 |
163 | ### Review the Infrastructure:
164 |
165 | Collect the DNS Name from the Load Balancer and access the homepage:
166 |
167 |
168 |
169 | Once the Application is pushed to the Repository, the CodePipeline will be triggered and CodeBuild will run the set of commands to dockerize the application and push it to the Amazon ECR repository. CodeBuild will in turn run the kubectl commands to create the Blue and Green services on the EKS clusters, if it does not exist. For the first time, it will pull the Flask demo Application from the Dockerhub and deploy it on Blue as well as Green service. It should say "Your Flask application is now running on a container in Amazon Web Services" as shown below:
170 |
171 |
172 |
173 |
174 |
175 | Go to Services -> CodePipeline -> Pipelines -> CdkStackEksALBBg-[unique-string]
176 | Review the Stages and the CodeBuild Projects to understand the implementation.
177 | Once the Application is deployed on the Green service, access it as mentioned above: http://ALB-DNS-name/?group=green.
178 |
179 | It is important to note that container exposes port 5000, whereas service exposes port 80 (for blue-service) OR 8080 (for green-service) which in turn is mapped to local host port on the EC2 worker node instance.
180 |
181 | After testing is completed, go to the Approval Stage and Click Approve. This will trigger the CodePipeline to execute the next stage to run the "Swap and Deploy" stage where it swaps the mapping of target-group to the blue / green service.
182 |
183 |
184 |
185 |
186 |
187 |
188 | ### Configuring for Canary Deployments:
189 |
190 | Configure your ALB for Canary based deployments using below commands from your Cloud9 terminal:
191 |
192 | ```bash
193 | cd /home/ec2-user/environment/amazon-eks-cicd-codebuild-eks-alb-bg/flask-docker-app/k8s
194 | kubectl apply -f flaskALBIngress_query2.yaml
195 | ```
196 |
197 |
198 |
199 | To bring the ALB config to the non-canary configuration, run the below commands:
200 |
201 | ```bash
202 | cd /home/ec2-user/environment/amazon-eks-cicd-codebuild-eks-alb-bg/flask-docker-app/k8s
203 | kubectl apply -f flaskALBIngress_query.yaml
204 | ```
205 |
206 | ### Cleanup
207 |
208 | (a) Remove the EKS Services:
209 | First connect to the kubernetes cluster using command published as "ConfigCommand" under CloudFormation output. Run "kubectl get svc" to check if you can see the Blue and Green services. Then run the below commands to delete the services.
210 |
211 | ```bash
212 | kubectl delete svc/flask-svc-alb-blue svc/flask-svc-alb-green -n flask-alb
213 | kubectl delete deploy/flask-deploy-alb-blue deploy/flask-deploy-alb-green -n flask-alb
214 | kubectl delete ingress alb-ingress -n flask-alb
215 | kubectl delete deploy alb-ingress-controller -n kube-system
216 | ```
217 |
218 | (b) Destroy the CDK Stack:
219 | Ensure you are in the cdk directory and then run the below command:
220 |
221 | ```bash
222 | cdk destroy
223 | ```
224 |
225 | (c ) Remove the individual created policies:
226 | Access IAM Service, then access Roles, then select the Worker Node Instance role (search by CdkStackALBEksBg-ClusterDefaultCapacityInstanceRol) and then remove the inline elb-policy
227 | Access IAM Service, then access Policies, then select “alb-ingress-controller” managed policy created for kubectl, then Select Policy Actions and Delete
228 |
229 | ### Conclusion:
230 |
231 | We built the CICD Pipeline using CDK to containerize and deploy a Python Flask based application using the Blue/Green Deployment method on Amazon EKS. We made the code change and saw it propagated through the CICD pipeline and deploy on Blue/Green service of EKS. We also configured and tested B/G Canary Deployment method.
232 |
233 | Hope you enjoyed the workshop!
234 |
235 |
236 | ## License
237 |
238 | This library is licensed under the MIT-0 License. See the LICENSE file.
239 |
--------------------------------------------------------------------------------
/archive-eks-alb-bg.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/archive-eks-alb-bg.tar.gz
--------------------------------------------------------------------------------
/cdk/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/cdk/.DS_Store
--------------------------------------------------------------------------------
/cdk/.gitignore:
--------------------------------------------------------------------------------
1 | *.js
2 | !jest.config.js
3 | *.d.ts
4 | node_modules
5 |
6 | # CDK asset staging directory
7 | .cdk.staging
8 | cdk.out
9 |
--------------------------------------------------------------------------------
/cdk/README.md:
--------------------------------------------------------------------------------
1 | # Useful commands
2 |
3 | * `npm run build` compile typescript to js
4 | * `npm run watch` watch for changes and compile
5 | * `npm run test` perform the jest unit tests
6 | * `cdk deploy` deploy this stack to your default AWS account/region
7 | * `cdk diff` compare deployed stack with current state
8 | * `cdk synth` emits the synthesized CloudFormation template
9 |
--------------------------------------------------------------------------------
/cdk/bin/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/cdk/bin/.DS_Store
--------------------------------------------------------------------------------
/cdk/bin/cdk.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import 'source-map-support/register';
3 | import cdk = require('@aws-cdk/core');
4 | import { CdkStackALBEksBg } from '../lib/cdk-stack';
5 |
6 | const app = new cdk.App();
7 |
8 | const env = {
9 | region: app.node.tryGetContext('region') || process.env.CDK_INTEG_REGION || process.env.CDK_DEFAULT_REGION,
10 | account: app.node.tryGetContext('account') || process.env.CDK_INTEG_ACCOUNT || process.env.CDK_DEFAULT_ACCOUNT
11 | };
12 |
13 |
14 | new CdkStackALBEksBg(app, 'CdkStackALBEksBg', { env });
15 |
--------------------------------------------------------------------------------
/cdk/cdk.json:
--------------------------------------------------------------------------------
1 | {
2 | "app": "npx ts-node bin/cdk.ts"
3 | }
4 |
--------------------------------------------------------------------------------
/cdk/lib/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/cdk/lib/.DS_Store
--------------------------------------------------------------------------------
/cdk/lib/cdk-stack.ts:
--------------------------------------------------------------------------------
1 |
2 | import cdk = require('@aws-cdk/core');
3 | import ec2 = require('@aws-cdk/aws-ec2');
4 | import ecr = require('@aws-cdk/aws-ecr');
5 | import eks = require('@aws-cdk/aws-eks');
6 | import iam = require('@aws-cdk/aws-iam');
7 | import codebuild = require('@aws-cdk/aws-codebuild');
8 | import codecommit = require('@aws-cdk/aws-codecommit');
9 | import targets = require('@aws-cdk/aws-events-targets');
10 | import codepipeline = require('@aws-cdk/aws-codepipeline');
11 | import codepipeline_actions = require('@aws-cdk/aws-codepipeline-actions');
12 |
13 |
14 |
15 | export class CdkStackALBEksBg extends cdk.Stack {
16 | constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
17 | super(scope, id, props);
18 |
19 | /**
20 | * Create a new VPC with single NAT Gateway
21 | */
22 | const vpc = new ec2.Vpc(this, 'NewVPC', {
23 | cidr: '10.0.0.0/16',
24 | natGateways: 1
25 | });
26 |
27 | const clusterAdmin = new iam.Role(this, 'AdminRole', {
28 | assumedBy: new iam.AccountRootPrincipal()
29 | });
30 |
31 | const controlPlaneSecurityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', {
32 | vpc,
33 | allowAllOutbound: true
34 | });
35 |
36 | controlPlaneSecurityGroup.addIngressRule(
37 | ec2.Peer.anyIpv4(),
38 | ec2.Port.tcp(80),
39 | "Allow all inbound traffic by default",
40 | );
41 |
42 | const cluster = new eks.Cluster(this, 'Cluster', {
43 | version: eks.KubernetesVersion.V1_21,
44 | securityGroup: controlPlaneSecurityGroup,
45 | vpc,
46 | defaultCapacity: 2,
47 | mastersRole: clusterAdmin,
48 | outputClusterName: true,
49 | });
50 |
51 | const ecrRepo = new ecr.Repository(this, 'EcrRepo');
52 |
53 | const repository = new codecommit.Repository(this, 'CodeCommitRepo', {
54 | repositoryName: `${this.stackName}-repo`
55 | });
56 |
57 |
58 |
59 | // CODEBUILD - project
60 | const project = new codebuild.Project(this, 'MyProject', {
61 | projectName: `${this.stackName}`,
62 | source: codebuild.Source.codeCommit({ repository }),
63 | environment: {
64 | buildImage: codebuild.LinuxBuildImage.fromAsset(this, 'CustomImage', {
65 | directory: '../dockerAssets.d',
66 | }),
67 | privileged: true
68 | },
69 | environmentVariables: {
70 | 'CLUSTER_NAME': {
71 | value: `${cluster.clusterName}`
72 | },
73 | 'ECR_REPO_URI': {
74 | value: `${ecrRepo.repositoryUri}`
75 | }
76 | },
77 | buildSpec: codebuild.BuildSpec.fromObject({
78 | version: "0.2",
79 | phases: {
80 | pre_build: {
81 | commands: [
82 | 'env',
83 | 'export TAG=${CODEBUILD_RESOLVED_SOURCE_VERSION}',
84 | 'export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output=text)',
85 | '/usr/local/bin/entrypoint.sh',
86 | 'echo Logging in to Amazon ECR',
87 | 'aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com'
88 | ]
89 | },
90 | build: {
91 | commands: [
92 | 'cd flask-docker-app',
93 | `docker build -t $ECR_REPO_URI:$TAG .`,
94 | 'docker push $ECR_REPO_URI:$TAG'
95 | ]
96 | },
97 | post_build: {
98 | commands: [
99 | 'kubectl get nodes -n flask-alb',
100 | 'kubectl get deploy -n flask-alb',
101 | 'kubectl get svc -n flask-alb',
102 | "isDeployed=$(kubectl get deploy -n flask-alb -o json | jq '.items[0]')",
103 | "deploy8080=$(kubectl get svc -n flask-alb -o wide | grep 8080: | tr ' ' '\n' | grep app= | sed 's/app=//g')",
104 | "echo $isDeployed $deploy8080",
105 | "if [[ \"$isDeployed\" == \"null\" ]]; then kubectl apply -f k8s/flaskALBBlue.yaml && kubectl apply -f k8s/flaskALBGreen.yaml; else kubectl set image deployment/$deploy8080 -n flask-alb flask=$ECR_REPO_URI:$TAG; fi",
106 | 'kubectl get deploy -n flask-alb',
107 | 'kubectl get svc -n flask-alb'
108 | ]
109 | }
110 | }
111 | })
112 | })
113 |
114 |
115 |
116 |
117 | // CODEBUILD - project2
118 | const project2 = new codebuild.Project(this, 'MyProject2', {
119 | projectName: `${this.stackName}2`,
120 | source: codebuild.Source.codeCommit({ repository }),
121 | environment: {
122 | buildImage: codebuild.LinuxBuildImage.fromAsset(this, 'CustomImage2', {
123 | directory: '../dockerAssets.d',
124 | }),
125 | privileged: true
126 | },
127 | environmentVariables: {
128 | 'CLUSTER_NAME': {
129 | value: `${cluster.clusterName}`
130 | },
131 | 'ECR_REPO_URI': {
132 | value: `${ecrRepo.repositoryUri}`
133 | }
134 | },
135 | buildSpec: codebuild.BuildSpec.fromObject({
136 | version: "0.2",
137 | phases: {
138 | pre_build: {
139 | commands: [
140 | 'env',
141 | 'export TAG=${CODEBUILD_RESOLVED_SOURCE_VERSION}',
142 | '/usr/local/bin/entrypoint.sh'
143 | ]
144 | },
145 | build: {
146 | commands: [
147 | 'cd flask-docker-app',
148 | 'echo "Dummy Action"'
149 | ]
150 | },
151 | post_build: {
152 | commands: [
153 | 'kubectl get nodes -n flask-alb',
154 | 'kubectl get deploy -n flask-alb',
155 | 'kubectl get svc -n flask-alb',
156 | "deploy8080=$(kubectl get svc -n flask-alb -o wide | grep ' 8080:' | tr ' ' '\n' | grep app= | sed 's/app=//g')",
157 | "deploy80=$(kubectl get svc -n flask-alb -o wide | grep ' 80:' | tr ' ' '\n' | grep app= | sed 's/app=//g')",
158 | "echo $deploy80 $deploy8080",
159 | "kubectl patch svc flask-svc-alb-blue -n flask-alb -p '{\"spec\":{\"selector\": {\"app\": \"'$deploy8080'\"}}}'",
160 | "kubectl patch svc flask-svc-alb-green -n flask-alb -p '{\"spec\":{\"selector\": {\"app\": \"'$deploy80'\"}}}'",
161 | 'kubectl get deploy -n flask-alb',
162 | 'kubectl get svc -n flask-alb'
163 | ]
164 | }
165 | }
166 | })
167 | })
168 |
169 |
170 |
171 |
172 |
173 | // PIPELINE
174 |
175 | const sourceOutput = new codepipeline.Artifact();
176 |
177 | const sourceAction = new codepipeline_actions.CodeCommitSourceAction({
178 | actionName: 'CodeCommit',
179 | repository,
180 | output: sourceOutput,
181 | });
182 |
183 | const buildAction = new codepipeline_actions.CodeBuildAction({
184 | actionName: 'CodeBuild',
185 | project: project,
186 | input: sourceOutput,
187 | outputs: [new codepipeline.Artifact()], // optional
188 | });
189 |
190 |
191 | const buildAction2 = new codepipeline_actions.CodeBuildAction({
192 | actionName: 'CodeBuild',
193 | project: project2,
194 | input: sourceOutput,
195 | });
196 |
197 |
198 | const manualApprovalAction = new codepipeline_actions.ManualApprovalAction({
199 | actionName: 'Approve',
200 | });
201 |
202 |
203 |
204 | new codepipeline.Pipeline(this, 'MyPipeline', {
205 | stages: [
206 | {
207 | stageName: 'Source',
208 | actions: [sourceAction],
209 | },
210 | {
211 | stageName: 'BuildAndDeploy',
212 | actions: [buildAction],
213 | },
214 | {
215 | stageName: 'ApproveSwapBG',
216 | actions: [manualApprovalAction],
217 | },
218 | {
219 | stageName: 'SwapBG',
220 | actions: [buildAction2],
221 | },
222 | ],
223 | });
224 |
225 |
226 | repository.onCommit('OnCommit', {
227 | target: new targets.CodeBuildProject(project)
228 | });
229 |
230 | ecrRepo.grantPullPush(project.role!)
231 | cluster.awsAuth.addMastersRole(project.role!)
232 | project.addToRolePolicy(new iam.PolicyStatement({
233 | actions: ['eks:DescribeCluster'],
234 | resources: [`${cluster.clusterArn}`],
235 | }))
236 |
237 |
238 | ecrRepo.grantPullPush(project2.role!)
239 | cluster.awsAuth.addMastersRole(project2.role!)
240 | project2.addToRolePolicy(new iam.PolicyStatement({
241 | actions: ['eks:DescribeCluster'],
242 | resources: [`${cluster.clusterArn}`],
243 | }))
244 |
245 |
246 | new cdk.CfnOutput(this, 'CodeCommitRepoName', { value: `${repository.repositoryName}` })
247 | new cdk.CfnOutput(this, 'CodeCommitRepoArn', { value: `${repository.repositoryArn}` })
248 | new cdk.CfnOutput(this, 'CodeCommitCloneUrlSsh', { value: `${repository.repositoryCloneUrlSsh}` })
249 | new cdk.CfnOutput(this, 'CodeCommitCloneUrlHttp', { value: `${repository.repositoryCloneUrlHttp}` })
250 | }
251 | }
252 |
--------------------------------------------------------------------------------
/cdk/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cdk",
3 | "version": "0.1.0",
4 | "bin": {
5 | "cdk": "bin/cdk.js"
6 | },
7 | "scripts": {
8 | "build": "tsc",
9 | "watch": "tsc -w",
10 | "test": "jest",
11 | "cdk": "cdk"
12 | },
13 | "devDependencies": {
14 | "@aws-cdk/assert": "1.134.0",
15 | "@types/jest": "27.0.3",
16 | "jest": "27.3.1",
17 | "ts-jest": "27.0.7",
18 | "aws-cdk": "1.134.0",
19 | "ts-node": "10.4.0",
20 | "typescript": "4.5.2"
21 | },
22 | "dependencies": {
23 | "@aws-cdk/aws-codebuild": "1.134.0",
24 | "@aws-cdk/aws-ec2": "1.134.0",
25 | "@aws-cdk/aws-ecr": "1.134.0",
26 | "@aws-cdk/aws-ecs": "1.134.0",
27 | "@aws-cdk/aws-eks": "1.134.0",
28 | "@aws-cdk/aws-events-targets": "1.134.0",
29 | "@aws-cdk/core": "1.134.0",
30 | "@aws-cdk/aws-codepipeline": "1.134.0",
31 | "@aws-cdk/aws-codepipeline-actions": "1.134.0",
32 | "@types/node": "16.11.10",
33 | "source-map-support": "0.5.21"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/cdk/test/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/cdk/test/.DS_Store
--------------------------------------------------------------------------------
/cdk/test/cdk.test.ts:
--------------------------------------------------------------------------------
1 | import { expect as expectCDK, matchTemplate, MatchStyle } from '@aws-cdk/assert';
2 | import cdk = require('@aws-cdk/core');
3 | import Cdk = require('../lib/cdk-stack');
4 |
5 | test('Empty Stack', () => {
6 | const app = new cdk.App();
7 | // WHEN
8 | const stack = new Cdk.CdkStackALBEksBg(app, 'MyTestStack');
9 | // THEN
10 | expectCDK(stack).to(matchTemplate({
11 | "Resources": {}
12 | }, MatchStyle.EXACT))
13 | });
14 |
--------------------------------------------------------------------------------
/cdk/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target":"ES2018",
4 | "module": "commonjs",
5 | "lib": ["es2016", "es2017.object", "es2017.string"],
6 | "declaration": true,
7 | "strict": true,
8 | "noImplicitAny": true,
9 | "strictNullChecks": true,
10 | "noImplicitThis": true,
11 | "alwaysStrict": true,
12 | "noUnusedLocals": false,
13 | "noUnusedParameters": false,
14 | "noImplicitReturns": true,
15 | "noFallthroughCasesInSwitch": false,
16 | "inlineSourceMap": true,
17 | "inlineSources": true,
18 | "experimentalDecorators": true,
19 | "strictPropertyInitialization":false,
20 | "typeRoots": ["./node_modules/@types"]
21 | },
22 | "exclude": ["cdk.out"]
23 | }
24 |
--------------------------------------------------------------------------------
/cicd/Jenkinsfile:
--------------------------------------------------------------------------------
1 | pipeline {
2 | agent {
3 | label 'master'
4 | }
5 | stages {
6 | stage('Pre-Build Stage') {
7 | steps {
8 | withCredentials([[ $class: 'AmazonWebServicesCredentialsBinding', credentialsId: 'e4e458ba-8178-4cc7-af47-cbd9046e76ce', accessKeyVariable: 'AWS_ACCESS_KEY_ID', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) {
9 | sh '''
10 | echo "Setting Environment Variables Stage"
11 | export AWS_REGION=${JENKINS_AWS_REGION}
12 | aws configure set region ${AWS_REGION}
13 | export ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
14 | ecrRepo=$(aws cloudformation --region ${AWS_REGION} describe-stack-resources --stack-name CdkStackALBEksBg | jq \'.StackResources[]|select(.ResourceType == "AWS::ECR::Repository").PhysicalResourceId\' | tr -d \'"\')
15 | preEcrRepo="${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/"
16 | export ECR_REPO_URI=$preEcrRepo$ecrRepo
17 | echo $ECR_REPO_URI
18 | export kubectlcmd=$(aws cloudformation --region ${AWS_REGION} describe-stacks --stack-name CdkStackALBEksBg | jq \'.Stacks[].Outputs[1].OutputValue\' | tr -d \'"\')
19 | kubeClusterName=$(aws cloudformation --region ${AWS_REGION} describe-stacks --stack-name CdkStackALBEksBg | jq \'.Stacks[].Outputs[0].OutputValue\' | tr -d \'"\')
20 | export CLUSTER_NAME=$kubeClusterName
21 | export ECR_REPO_URI=$ECR_REPO_URI
22 | CODEBUILD_RESOLVED_SOURCE_VERSION=01234abcdefgh
23 | export TAG=${CODEBUILD_RESOLVED_SOURCE_VERSION}
24 |
25 | set > ~/.vars
26 | cat ~/.vars
27 | '''
28 | }
29 | }
30 | }
31 |
32 | stage ('Build & Containerize Stage') {
33 | steps {
34 | withCredentials([[ $class: 'AmazonWebServicesCredentialsBinding', credentialsId: 'e4e458ba-8178-4cc7-af47-cbd9046e76ce', accessKeyVariable: 'AWS_ACCESS_KEY_ID', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) {
35 | sh label: '', script: '''echo "==================="
36 | echo "Build Stage"
37 | echo "==================="
38 |
39 | echo "++++++++++++++++"
40 | source ~/.vars
41 | eval $kubectlcmd
42 | aws configure set region ${AWS_REGION}
43 | echo "++++++++++++++++"
44 |
45 |
46 | pwd
47 | ls -al
48 | cd amazon-eks-cicd-codebuild-eks-alb-bg/flask-docker-app
49 | $(aws ecr get-login --no-include-email)
50 | docker build -t $ECR_REPO_URI:$TAG .
51 | docker push $ECR_REPO_URI:$TAG
52 | '''
53 | }
54 | }
55 | }
56 |
57 | stage ('Deploy to Green Env Stage') {
58 | steps {
59 | withCredentials([[ $class: 'AmazonWebServicesCredentialsBinding', credentialsId: 'e4e458ba-8178-4cc7-af47-cbd9046e76ce', accessKeyVariable: 'AWS_ACCESS_KEY_ID', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) {
60 | sh label: '', script: '''echo "==================="
61 | echo "Post-Build Stage"
62 | echo "==================="
63 |
64 | echo "++++++++++++++++"
65 | source ~/.vars
66 | eval $kubectlcmd
67 | aws configure set region ${AWS_REGION}
68 | echo "++++++++++++++++"
69 |
70 | kubectl get nodes -n flask-alb
71 | kubectl get deploy -n flask-alb
72 | kubectl get svc -n flask-alb
73 | isDeployed=$(kubectl get deploy -n flask-alb -o json | jq \'.items[0]\')
74 | deploy8080=$(kubectl get svc -n flask-alb -o wide | grep 8080: | tr \' \' \'\\n\' | grep app= | sed \'s/app=//g\')
75 |
76 | if [[ \\"$isDeployed\\" == \\"null\\" ]]; then kubectl apply -f k8s/flaskALBBlue.yaml && kubectl apply -f k8s/flaskALBGreen.yaml; else kubectl set image deployment/$deploy8080 -n flask-alb flask=$ECR_REPO_URI:$TAG; fi
77 |
78 | kubectl get deploy -n flask-alb
79 | kubectl get svc -n flask-alb
80 | '''
81 | }
82 | }
83 | }
84 |
85 | stage ('Approval & Swap Blue/Green Env Stage') {
86 | input {
87 | message "Do you want to proceed for production deployment?"
88 | }
89 | steps {
90 | withCredentials([[ $class: 'AmazonWebServicesCredentialsBinding', credentialsId: 'e4e458ba-8178-4cc7-af47-cbd9046e76ce', accessKeyVariable: 'AWS_ACCESS_KEY_ID', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) {
91 | sh label: '', script: '''echo "==================="
92 | echo "Swap Stage"
93 | echo "==================="
94 |
95 | echo "++++++++++++++++"
96 | source ~/.vars
97 | eval $kubectlcmd
98 | aws configure set region ${AWS_REGION}
99 | echo "++++++++++++++++"
100 |
101 | kubectl get nodes -n flask-alb
102 | kubectl get deploy -n flask-alb
103 | kubectl get svc -n flask-alb
104 | deploy8080=$(kubectl get svc -n flask-alb -o wide | grep ' 8080:' | tr ' ' '\n' | grep app= | sed 's/app=//g')
105 | deploy80=$(kubectl get svc -n flask-alb -o wide | grep ' 80:' | tr ' ' '\n' | grep app= | sed 's/app=//g')
106 | echo $deploy80 $deploy8080
107 | kubectl patch svc flask-svc-alb-blue -n flask-alb -p '{\"spec\":{\"selector\": {\"app\": \"'$deploy8080'\"}}}'
108 | kubectl patch svc flask-svc-alb-green -n flask-alb -p '{\"spec\":{\"selector\": {\"app\": \"'$deploy80'\"}}}'
109 | kubectl get deploy -n flask-alb
110 | kubectl get svc -n flask-alb
111 | '''
112 | }
113 | }
114 | }
115 |
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/cicd/jenkins-ecs-workshop.json:
--------------------------------------------------------------------------------
1 | {
2 | "AWSTemplateFormatVersion": "2010-09-09",
3 | "Description": "Cloudformation stack for Jenkins ECS",
4 | "Parameters": {
5 | "KeyName": {
6 | "Description": "Optional: the EC2 Key Pair to allow SSH access to the instances",
7 | "Type": "String",
8 | "Default": ""
9 | },
10 | "AllowedIPRange": {
11 | "Description": "The public IP address range that can be used to connect to the instances",
12 | "Type": "String",
13 | "MinLength": "9",
14 | "MaxLength": "18",
15 | "Default": "0.0.0.0/0",
16 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
17 | "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
18 | },
19 | "VPCIPRange": {
20 | "Description": "The private IP address range for allocating IPs within the VPC.",
21 | "Type": "String",
22 | "MinLength": "9",
23 | "MaxLength": "18",
24 | "Default": "10.0.0.0/16",
25 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
26 | "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
27 | },
28 | "InstanceType": {
29 | "Description": "EC2 instance type",
30 | "Type": "String",
31 | "Default": "t2.medium",
32 | "AllowedValues": [
33 | "t2.micro",
34 | "t2.small",
35 | "t2.medium"
36 | ],
37 | "ConstraintDescription": "Must be a valid EC2 instance type."
38 | },
39 | "DockerImage": {
40 | "Description": "The docker image to use for Jenkins",
41 | "Type": "String",
42 | "Default": "nikunjv/jenkins:v4"
43 | }
44 | },
45 | "Mappings": {
46 | "RegionAmazonECSOptimizedAMIMapping": {
47 | "us-east-2": {
48 | "AMI": "ami-0c0415cdff14e2a4a"
49 | },
50 | "us-east-1": {
51 | "AMI": "ami-098616968d61e549e"
52 | },
53 | "us-west-2": {
54 | "AMI": "ami-014a2e30da708ee8b"
55 | },
56 | "us-west-1": {
57 | "AMI": "ami-514e6431"
58 | },
59 | "eu-west-2": {
60 | "AMI": "ami-0a85946e"
61 | },
62 | "eu-west-1": {
63 | "AMI": "ami-bd7e8dc4"
64 | },
65 | "eu-central-1": {
66 | "AMI": "ami-f15ff69e"
67 | },
68 | "ap-northeast-1": {
69 | "AMI": "ami-ab5ea9cd"
70 | },
71 | "ap-southeast-2": {
72 | "AMI": "ami-c3233ba0"
73 | },
74 | "ap-southeast-1": {
75 | "AMI": "ami-ae0b91cd"
76 | },
77 | "ca-central-1": {
78 | "AMI": "ami-32bb0556"
79 | }
80 | }
81 | },
82 | "Conditions": {
83 | "HasKeyName": {"Fn::Not": [{"Fn::Equals": ["", {"Ref": "KeyName"}]}]}
84 | },
85 | "Resources": {
86 | "VPC": {
87 | "Type": "AWS::EC2::VPC",
88 | "Properties": {
89 | "EnableDnsSupport": "true",
90 | "EnableDnsHostnames": "true",
91 | "CidrBlock": {"Ref": "VPCIPRange"},
92 | "Tags": [
93 | {
94 | "Key": "Application",
95 | "Value": {
96 | "Ref": "AWS::StackId"
97 | }
98 | }
99 | ]
100 | }
101 | },
102 | "InternetGateway": {
103 | "Type": "AWS::EC2::InternetGateway",
104 | "Properties": {
105 | "Tags": [
106 | {
107 | "Key": "Application",
108 | "Value": {
109 | "Ref": "AWS::StackName"
110 | }
111 | },
112 | {
113 | "Key": "Network",
114 | "Value": "Public"
115 | }
116 | ]
117 | }
118 | },
119 | "GatewayToInternet": {
120 | "Type": "AWS::EC2::VPCGatewayAttachment",
121 | "Properties": {
122 | "VpcId": {
123 | "Ref": "VPC"
124 | },
125 | "InternetGatewayId": {
126 | "Ref": "InternetGateway"
127 | }
128 | }
129 | },
130 | "RouteTable": {
131 | "Type": "AWS::EC2::RouteTable",
132 | "Properties": {
133 | "VpcId": {
134 | "Ref": "VPC"
135 | }
136 | }
137 | },
138 | "SubnetRouteTableAssoc": {
139 | "Type": "AWS::EC2::SubnetRouteTableAssociation",
140 | "Properties": {
141 | "RouteTableId": {
142 | "Ref": "RouteTable"
143 | },
144 | "SubnetId": {
145 | "Ref": "Subnet"
146 | }
147 | }
148 | },
149 | "InternetGatewayRoute": {
150 | "Type": "AWS::EC2::Route",
151 | "Properties": {
152 | "DestinationCidrBlock": "0.0.0.0/0",
153 | "RouteTableId": {
154 | "Ref": "RouteTable"
155 | },
156 | "GatewayId": {
157 | "Ref": "InternetGateway"
158 | }
159 | }
160 | },
161 | "Subnet": {
162 | "Type": "AWS::EC2::Subnet",
163 | "Properties": {
164 | "VpcId": {
165 | "Ref": "VPC"
166 | },
167 | "CidrBlock": {"Ref": "VPCIPRange"},
168 | "Tags": [
169 | {
170 | "Key": "Application",
171 | "Value": {
172 | "Ref": "AWS::StackId"
173 | }
174 | }
175 | ]
176 | }
177 | },
178 | "ECSServiceRole":{
179 | "Type":"AWS::IAM::Role",
180 | "Properties":{
181 | "AssumeRolePolicyDocument":{
182 | "Statement":[
183 | {
184 | "Effect":"Allow",
185 | "Principal":{
186 | "Service":[
187 | "ecs.amazonaws.com"
188 | ]
189 | },
190 | "Action":[
191 | "sts:AssumeRole"
192 | ]
193 | }
194 | ]
195 | },
196 | "Path":"/",
197 | "Policies":[
198 | {
199 | "PolicyName":"ecs-service",
200 | "PolicyDocument":{
201 | "Statement":[
202 | {
203 | "Effect":"Allow",
204 | "Action":[
205 | "elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
206 | "elasticloadbalancing:DeregisterTargets",
207 | "elasticloadbalancing:Describe*",
208 | "elasticloadbalancing:RegisterInstancesWithLoadBalancer",
209 | "elasticloadbalancing:RegisterTargets",
210 | "ec2:Describe*",
211 | "ec2:AuthorizeSecurityGroupIngress"
212 | ],
213 | "Resource":"*"
214 | }
215 | ]
216 | }
217 | }
218 | ]
219 | }
220 | },
221 | "EC2Role":{
222 | "Type":"AWS::IAM::Role",
223 | "Properties":{
224 | "AssumeRolePolicyDocument":{
225 | "Statement":[
226 | {
227 | "Effect":"Allow",
228 | "Principal":{
229 | "Service":[
230 | "ec2.amazonaws.com"
231 | ]
232 | },
233 | "Action":[
234 | "sts:AssumeRole"
235 | ]
236 | }
237 | ]
238 | },
239 | "Path":"/",
240 | "Policies":[
241 | {
242 | "PolicyName":"ecs-service",
243 | "PolicyDocument":{
244 | "Statement":[
245 | {
246 | "Effect":"Allow",
247 | "Action":[
248 | "ecs:*",
249 | "elasticloadbalancing:Describe*",
250 | "logs:CreateLogStream",
251 | "logs:PutLogEvents"
252 | ],
253 | "Resource":"*"
254 | }
255 | ]
256 | }
257 | }
258 | ]
259 | }
260 | },
261 | "JenkinsECSInstanceProfile": {
262 | "Type": "AWS::IAM::InstanceProfile",
263 | "Properties": {
264 | "Path": "/",
265 | "Roles": [
266 | {
267 | "Ref": "EC2Role"
268 | }
269 | ]
270 | }
271 | },
272 | "JenkinsSecurityGroup": {
273 | "Type": "AWS::EC2::SecurityGroup",
274 | "Properties": {
275 | "GroupDescription": "SecurityGroup for Jenkins instances: master and slaves",
276 | "VpcId": {
277 | "Ref": "VPC"
278 | },
279 | "SecurityGroupIngress": [
280 | {
281 | "IpProtocol": "tcp",
282 | "FromPort": "22",
283 | "ToPort": "22",
284 | "CidrIp": {
285 | "Ref": "AllowedIPRange"
286 | }
287 | },
288 | {
289 | "IpProtocol": "tcp",
290 | "FromPort": "8080",
291 | "ToPort": "8080",
292 | "CidrIp": {
293 | "Ref": "VPCIPRange"
294 | }
295 | },
296 | {
297 | "IpProtocol": "tcp",
298 | "FromPort": "50000",
299 | "ToPort": "50000",
300 | "CidrIp": {
301 | "Ref": "VPCIPRange"
302 | }
303 | }
304 | ]
305 | }
306 | },
307 | "JenkinsELBSecurityGroup": {
308 | "Type": "AWS::EC2::SecurityGroup",
309 | "Properties": {
310 | "GroupDescription": "SecurityGroup for Jenkins ELB",
311 | "VpcId": {
312 | "Ref": "VPC"
313 | },
314 | "SecurityGroupIngress": [
315 | {
316 | "IpProtocol": "tcp",
317 | "FromPort": "80",
318 | "ToPort": "80",
319 | "CidrIp": {
320 | "Ref": "AllowedIPRange"
321 | }
322 | }
323 | ]
324 | }
325 | },
326 | "EFSSecurityGroup": {
327 | "Type": "AWS::EC2::SecurityGroup",
328 | "Properties": {
329 | "GroupDescription": "Security group for EFS mount target",
330 | "VpcId": {
331 | "Ref": "VPC"
332 | },
333 | "SecurityGroupIngress": [
334 | {
335 | "IpProtocol": "tcp",
336 | "FromPort": "2049",
337 | "ToPort": "2049",
338 | "CidrIp": {
339 | "Ref": "VPCIPRange"
340 | }
341 | }
342 | ]
343 | }
344 | },
345 | "JenkinsEFS": {
346 | "Type": "AWS::EFS::FileSystem",
347 | "Properties": {
348 | "FileSystemTags": [
349 | {
350 | "Key": "Name",
351 | "Value": "JenkinsEFS"
352 | }
353 | ]
354 | }
355 | },
356 | "MountTarget": {
357 | "Type": "AWS::EFS::MountTarget",
358 | "Properties": {
359 | "FileSystemId": {
360 | "Ref": "JenkinsEFS"
361 | },
362 | "SubnetId": {
363 | "Ref": "Subnet"
364 | },
365 | "SecurityGroups": [
366 | {
367 | "Ref": "EFSSecurityGroup"
368 | }
369 | ]
370 | }
371 | },
372 | "JenkinsELB": {
373 | "Type": "AWS::ElasticLoadBalancing::LoadBalancer",
374 | "Properties": {
375 | "LoadBalancerName": "jenkins-elb",
376 | "Scheme": "internet-facing",
377 | "Subnets": [
378 | {
379 | "Ref": "Subnet"
380 | }
381 | ],
382 | "SecurityGroups": [
383 | {
384 | "Ref": "JenkinsELBSecurityGroup"
385 | }
386 | ],
387 | "Listeners": [
388 | {
389 | "InstancePort": "8080",
390 | "InstanceProtocol": "HTTP",
391 | "LoadBalancerPort": "80",
392 | "Protocol": "HTTP",
393 | "PolicyNames": [
394 | "JenkinsELBStickiness"
395 | ]
396 | }
397 | ],
398 | "LBCookieStickinessPolicy": [
399 | {
400 | "CookieExpirationPeriod": "3600",
401 | "PolicyName": "JenkinsELBStickiness"
402 | }
403 | ],
404 | "HealthCheck": {
405 | "HealthyThreshold": "3",
406 | "Interval": "20",
407 | "Target": "HTTP:8080/login",
408 | "Timeout": "2",
409 | "UnhealthyThreshold": "10"
410 | }
411 | }
412 | },
413 | "JenkinsCluster": {
414 | "Type": "AWS::ECS::Cluster",
415 | "Properties": {
416 | "ClusterName": "jenkins-cluster"
417 | }
418 | },
419 | "JenkinsMasterTaskDefinition": {
420 | "Type": "AWS::ECS::TaskDefinition",
421 | "Properties": {
422 | "Family": "jenkins-master",
423 | "NetworkMode": "bridge",
424 | "ContainerDefinitions": [
425 | {
426 | "Name": "jenkins-master",
427 | "Image": {
428 | "Ref": "DockerImage"
429 | },
430 | "MountPoints": [
431 | {
432 | "SourceVolume": "data-volume",
433 | "ContainerPath": "/var/jenkins_home"
434 | },
435 | {
436 | "SourceVolume": "docker-volume",
437 | "ContainerPath": "/var/run/docker.sock"
438 | }
439 | ],
440 | "Essential": true,
441 | "Cpu": 3,
442 | "MemoryReservation": 2048,
443 | "PortMappings": [
444 | {
445 | "HostPort": 8080,
446 | "ContainerPort": 8080,
447 | "Protocol": "tcp"
448 | },
449 | {
450 | "HostPort": 50000,
451 | "ContainerPort": 50000,
452 | "Protocol": "tcp"
453 | }
454 | ]
455 | }
456 | ],
457 | "Volumes": [
458 | {
459 | "Host": {
460 | "SourcePath": "/data/"
461 | },
462 | "Name": "data-volume"
463 | },
464 | {
465 | "Host": {
466 | "SourcePath": "/var/run/docker.sock"
467 | },
468 | "Name": "docker-volume"
469 | }
470 | ]
471 | }
472 | },
473 | "JenkinsECSService": {
474 | "DependsOn": ["JenkinsELB"],
475 | "Type": "AWS::ECS::Service",
476 | "Properties": {
477 | "Cluster": "jenkins-cluster",
478 | "DesiredCount": 1,
479 | "ServiceName": "jenkins-master",
480 | "TaskDefinition": {
481 | "Ref": "JenkinsMasterTaskDefinition"
482 | },
483 | "Role" : { "Ref" : "ECSServiceRole" },
484 | "LoadBalancers": [
485 | {
486 | "LoadBalancerName": "jenkins-elb",
487 | "ContainerPort": "8080",
488 | "ContainerName": "jenkins-master"
489 | }
490 | ]
491 | }
492 | },
493 | "JenkinsECSLaunchConfiguration": {
494 | "Type": "AWS::AutoScaling::LaunchConfiguration",
495 | "Properties": {
496 | "AssociatePublicIpAddress": true,
497 | "ImageId": {
498 | "Fn::FindInMap": [
499 | "RegionAmazonECSOptimizedAMIMapping",
500 | {
501 | "Ref": "AWS::Region"
502 | },
503 | "AMI"
504 | ]
505 | },
506 | "IamInstanceProfile": {
507 | "Ref": "JenkinsECSInstanceProfile"
508 | },
509 | "InstanceType": {
510 | "Ref": "InstanceType"
511 | },
512 | "KeyName": {"Fn::If": ["HasKeyName", {"Ref": "KeyName"}, {"Ref": "AWS::NoValue"}]},
513 | "SecurityGroups": [
514 | {
515 | "Ref": "JenkinsSecurityGroup"
516 | }
517 | ],
518 | "BlockDeviceMappings": [
519 | {
520 | "DeviceName": "/dev/xvdcz",
521 | "Ebs": {
522 | "VolumeSize": "24",
523 | "DeleteOnTermination": true
524 | }
525 | }
526 | ],
527 | "UserData": {
528 | "Fn::Base64": {
529 | "Fn::Join": [
530 | "",
531 | [
532 | "#!/bin/bash\n",
533 | "echo 'ECS_CLUSTER=jenkins-cluster' >> /etc/ecs/ecs.config\n",
534 | "#Mount EFS volume\n",
535 | "yum install -y nfs-utils\n",
536 | "EC2_AVAIL_ZONE=`curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone`\n",
537 | "EC2_REGION=",
538 | {
539 | "Ref": "AWS::Region"
540 | },
541 | "\n",
542 | "EFS_FILE_SYSTEM_ID=",
543 | {
544 | "Ref": "JenkinsEFS"
545 | },
546 | "\n",
547 | "EFS_PATH=$EC2_AVAIL_ZONE.$EFS_FILE_SYSTEM_ID.efs.$EC2_REGION.amazonaws.com\n",
548 | "mkdir /data\n",
549 | "mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 $EFS_PATH:/ /data\n",
550 | "#Give ownership to jenkins user\n",
551 | "chown 1000 /data\n",
552 | "chmod 777 /var/run/docker.sock\n",
553 | "if [[ ! -d /data/jobs/ ]]; then cd /data; curl -LJO https://github.com/aws-samples/amazon-eks-cdk-blue-green-cicd/raw/master/cicd/jenkins-jobs-archive.tar.gz; tar -xzvf jenkins-jobs-archive.tar.gz; cd; ls /data/jobs/; else echo 'SKIPPING DOWNLOAD...'; fi\n"
554 | ]
555 | ]
556 | }
557 | }
558 | }
559 | },
560 | "JenkinsECSAutoScaling": {
561 | "Type": "AWS::AutoScaling::AutoScalingGroup",
562 | "Properties": {
563 | "VPCZoneIdentifier": [
564 | {
565 | "Ref": "Subnet"
566 | }
567 | ],
568 | "LaunchConfigurationName": {
569 | "Ref": "JenkinsECSLaunchConfiguration"
570 | },
571 | "MinSize": "2",
572 | "MaxSize": "5",
573 | "DesiredCapacity": "2",
574 | "HealthCheckType": "EC2",
575 | "HealthCheckGracePeriod": "400",
576 | "Tags": [
577 | {
578 | "Key": "Name",
579 | "Value": "jenkins-ecs-instance",
580 | "PropagateAtLaunch": "true"
581 | }
582 | ]
583 | }
584 | },
585 |
586 | "JenkinsClusterScaleUpPolicy": {
587 | "Type" : "AWS::AutoScaling::ScalingPolicy",
588 | "Properties" : {
589 | "AdjustmentType" : "ChangeInCapacity",
590 | "AutoScalingGroupName" : { "Ref": "JenkinsECSAutoScaling" },
591 | "EstimatedInstanceWarmup" : 60,
592 | "MetricAggregationType" : "Average",
593 | "PolicyType" : "StepScaling",
594 | "StepAdjustments" : [ {
595 | "MetricIntervalLowerBound" : 0,
596 | "ScalingAdjustment" : 2
597 | }]
598 | }
599 | },
600 |
601 | "JenkinsClusterScaleUpAlarm" : {
602 | "Type" : "AWS::CloudWatch::Alarm",
603 | "Properties" : {
604 | "AlarmDescription" : "CPU utilization peaked at 70% during the last minute",
605 | "AlarmName" : "JenkinsClusterScaleUpAlarm",
606 | "AlarmActions": [ { "Ref": "JenkinsClusterScaleUpPolicy" } ],
607 | "Dimensions" : [{
608 | "Name": "ClusterName",
609 | "Value": "jenkins-cluster"
610 | }],
611 | "MetricName" : "CPUReservation",
612 | "Namespace" : "AWS/ECS",
613 | "ComparisonOperator" : "GreaterThanOrEqualToThreshold",
614 | "Statistic" : "Maximum",
615 | "Threshold" : 70,
616 | "Period" : 60,
617 | "EvaluationPeriods": 1,
618 | "TreatMissingData" : "notBreaching"
619 | }
620 | },
621 |
622 | "JenkinsClusterScaleDownPolicy": {
623 | "Type" : "AWS::AutoScaling::ScalingPolicy",
624 | "Properties" : {
625 | "AdjustmentType" : "PercentChangeInCapacity",
626 | "AutoScalingGroupName" : { "Ref": "JenkinsECSAutoScaling" },
627 | "Cooldown" : "120",
628 | "ScalingAdjustment" : "-50"
629 | }
630 | },
631 |
632 | "JenkinsClusterScaleDownAlarm" : {
633 | "Type" : "AWS::CloudWatch::Alarm",
634 | "Properties" : {
635 | "AlarmDescription" : "CPU utilization is under 50% for the last 10 min (change 10 min to 45 min for prod use as you pay by the hour )",
636 | "AlarmName" : "JenkinsClusterScaleDownAlarm",
637 | "AlarmActions": [ { "Ref": "JenkinsClusterScaleDownPolicy" } ],
638 | "Dimensions" : [{
639 | "Name": "ClusterName",
640 | "Value": "jenkins-cluster"
641 | }],
642 | "MetricName" : "CPUReservation",
643 | "Namespace" : "AWS/ECS",
644 | "ComparisonOperator" : "LessThanThreshold",
645 | "Statistic" : "Maximum",
646 | "Threshold" : 50,
647 | "Period" : 600,
648 | "EvaluationPeriods": 1,
649 | "TreatMissingData" : "notBreaching"
650 | }
651 | }
652 |
653 | },
654 | "Outputs" : {
655 | "JenkinsELB" : {
656 | "Description": "Jenkins URL",
657 | "Value" : {"Fn::Join": ["", ["http://", { "Fn::GetAtt" : [ "JenkinsELB", "DNSName" ]}] ]}
658 | }
659 | }
660 | }
661 |
--------------------------------------------------------------------------------
/cicd/jenkins-ecs-workshop2.json:
--------------------------------------------------------------------------------
1 | {
2 | "AWSTemplateFormatVersion": "2010-09-09",
3 | "Description": "Cloudformation stack for Jenkins ECS",
4 | "Parameters": {
5 | "KeyName": {
6 | "Description": "Optional: the EC2 Key Pair to allow SSH access to the instances",
7 | "Type": "String",
8 | "Default": ""
9 | },
10 | "AllowedIPRange": {
11 | "Description": "The public IP address range that can be used to connect to the instances",
12 | "Type": "String",
13 | "MinLength": "9",
14 | "MaxLength": "18",
15 | "Default": "0.0.0.0/0",
16 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
17 | "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
18 | },
19 | "VPCIPRange": {
20 | "Description": "The private IP address range for allocating IPs within the VPC.",
21 | "Type": "String",
22 | "MinLength": "9",
23 | "MaxLength": "18",
24 | "Default": "10.0.0.0/16",
25 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
26 | "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
27 | },
28 | "InstanceType": {
29 | "Description": "EC2 instance type",
30 | "Type": "String",
31 | "Default": "t2.medium",
32 | "AllowedValues": [
33 | "t2.micro",
34 | "t2.small",
35 | "t2.medium"
36 | ],
37 | "ConstraintDescription": "Must be a valid EC2 instance type."
38 | },
39 | "DockerImage": {
40 | "Description": "The docker image to use for Jenkins",
41 | "Type": "String",
42 | "Default": "nikunjv/jenkins:v4"
43 | }
44 | },
45 | "Mappings": {
46 | "RegionAmazonECSOptimizedAMIMapping": {
47 | "us-east-2": {
48 | "AMI": "ami-0c0415cdff14e2a4a"
49 | },
50 | "us-east-1": {
51 | "AMI": "ami-098616968d61e549e"
52 | },
53 | "us-west-2": {
54 | "AMI": "ami-014a2e30da708ee8b"
55 | },
56 | "us-west-1": {
57 | "AMI": "ami-00271233a1ebb9161"
58 | },
59 | "eu-west-2": {
60 | "AMI": "ami-02254c861b8371869"
61 | },
62 | "eu-west-1": {
63 | "AMI": "ami-0bb01c7d2705a4800"
64 | },
65 | "eu-central-1": {
66 | "AMI": "ami-01b521fb7540d9996"
67 | },
68 | "ap-northeast-1": {
69 | "AMI": "ami-0763fff45988661c8"
70 | },
71 | "ap-southeast-2": {
72 | "AMI": "ami-0a7c4f7f17d3eecbc"
73 | },
74 | "ap-southeast-1": {
75 | "AMI": "ami-0bd1daf5da8a9a903"
76 | },
77 | "ca-central-1": {
78 | "AMI": "ami-092262e997a1ab27b"
79 | }
80 | }
81 | },
82 | "Conditions": {
83 | "HasKeyName": {"Fn::Not": [{"Fn::Equals": ["", {"Ref": "KeyName"}]}]}
84 | },
85 | "Resources": {
86 | "VPC": {
87 | "Type": "AWS::EC2::VPC",
88 | "Properties": {
89 | "EnableDnsSupport": "true",
90 | "EnableDnsHostnames": "true",
91 | "CidrBlock": {"Ref": "VPCIPRange"},
92 | "Tags": [
93 | {
94 | "Key": "Application",
95 | "Value": {
96 | "Ref": "AWS::StackId"
97 | }
98 | }
99 | ]
100 | }
101 | },
102 | "InternetGateway": {
103 | "Type": "AWS::EC2::InternetGateway",
104 | "Properties": {
105 | "Tags": [
106 | {
107 | "Key": "Application",
108 | "Value": {
109 | "Ref": "AWS::StackName"
110 | }
111 | },
112 | {
113 | "Key": "Network",
114 | "Value": "Public"
115 | }
116 | ]
117 | }
118 | },
119 | "GatewayToInternet": {
120 | "Type": "AWS::EC2::VPCGatewayAttachment",
121 | "Properties": {
122 | "VpcId": {
123 | "Ref": "VPC"
124 | },
125 | "InternetGatewayId": {
126 | "Ref": "InternetGateway"
127 | }
128 | }
129 | },
130 | "RouteTable": {
131 | "Type": "AWS::EC2::RouteTable",
132 | "Properties": {
133 | "VpcId": {
134 | "Ref": "VPC"
135 | }
136 | }
137 | },
138 | "SubnetRouteTableAssoc": {
139 | "Type": "AWS::EC2::SubnetRouteTableAssociation",
140 | "Properties": {
141 | "RouteTableId": {
142 | "Ref": "RouteTable"
143 | },
144 | "SubnetId": {
145 | "Ref": "Subnet"
146 | }
147 | }
148 | },
149 | "InternetGatewayRoute": {
150 | "Type": "AWS::EC2::Route",
151 | "Properties": {
152 | "DestinationCidrBlock": "0.0.0.0/0",
153 | "RouteTableId": {
154 | "Ref": "RouteTable"
155 | },
156 | "GatewayId": {
157 | "Ref": "InternetGateway"
158 | }
159 | }
160 | },
161 | "Subnet": {
162 | "Type": "AWS::EC2::Subnet",
163 | "Properties": {
164 | "VpcId": {
165 | "Ref": "VPC"
166 | },
167 | "CidrBlock": {"Ref": "VPCIPRange"},
168 | "Tags": [
169 | {
170 | "Key": "Application",
171 | "Value": {
172 | "Ref": "AWS::StackId"
173 | }
174 | }
175 | ]
176 | }
177 | },
178 | "ECSServiceRole":{
179 | "Type":"AWS::IAM::Role",
180 | "Properties":{
181 | "AssumeRolePolicyDocument":{
182 | "Statement":[
183 | {
184 | "Effect":"Allow",
185 | "Principal":{
186 | "Service":[
187 | "ecs.amazonaws.com"
188 | ]
189 | },
190 | "Action":[
191 | "sts:AssumeRole"
192 | ]
193 | }
194 | ]
195 | },
196 | "Path":"/",
197 | "Policies":[
198 | {
199 | "PolicyName":"ecs-service",
200 | "PolicyDocument":{
201 | "Statement":[
202 | {
203 | "Effect":"Allow",
204 | "Action":[
205 | "elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
206 | "elasticloadbalancing:DeregisterTargets",
207 | "elasticloadbalancing:Describe*",
208 | "elasticloadbalancing:RegisterInstancesWithLoadBalancer",
209 | "elasticloadbalancing:RegisterTargets",
210 | "ec2:Describe*",
211 | "ec2:AuthorizeSecurityGroupIngress"
212 | ],
213 | "Resource":"*"
214 | }
215 | ]
216 | }
217 | }
218 | ]
219 | }
220 | },
221 | "EC2Role":{
222 | "Type":"AWS::IAM::Role",
223 | "Properties":{
224 | "AssumeRolePolicyDocument":{
225 | "Statement":[
226 | {
227 | "Effect":"Allow",
228 | "Principal":{
229 | "Service":[
230 | "ec2.amazonaws.com"
231 | ]
232 | },
233 | "Action":[
234 | "sts:AssumeRole"
235 | ]
236 | }
237 | ]
238 | },
239 | "Path":"/",
240 | "Policies":[
241 | {
242 | "PolicyName":"ecs-service",
243 | "PolicyDocument":{
244 | "Statement":[
245 | {
246 | "Effect":"Allow",
247 | "Action":[
248 | "ecs:*",
249 | "elasticloadbalancing:Describe*",
250 | "logs:CreateLogStream",
251 | "logs:PutLogEvents"
252 | ],
253 | "Resource":"*"
254 | }
255 | ]
256 | }
257 | }
258 | ]
259 | }
260 | },
261 | "JenkinsECSInstanceProfile": {
262 | "Type": "AWS::IAM::InstanceProfile",
263 | "Properties": {
264 | "Path": "/",
265 | "Roles": [
266 | {
267 | "Ref": "EC2Role"
268 | }
269 | ]
270 | }
271 | },
272 | "JenkinsSecurityGroup": {
273 | "Type": "AWS::EC2::SecurityGroup",
274 | "Properties": {
275 | "GroupDescription": "SecurityGroup for Jenkins instances: master and slaves",
276 | "VpcId": {
277 | "Ref": "VPC"
278 | },
279 | "SecurityGroupIngress": [
280 | {
281 | "IpProtocol": "tcp",
282 | "FromPort": "22",
283 | "ToPort": "22",
284 | "CidrIp": {
285 | "Ref": "AllowedIPRange"
286 | }
287 | },
288 | {
289 | "IpProtocol": "tcp",
290 | "FromPort": "8080",
291 | "ToPort": "8080",
292 | "CidrIp": {
293 | "Ref": "VPCIPRange"
294 | }
295 | },
296 | {
297 | "IpProtocol": "tcp",
298 | "FromPort": "50000",
299 | "ToPort": "50000",
300 | "CidrIp": {
301 | "Ref": "VPCIPRange"
302 | }
303 | }
304 | ]
305 | }
306 | },
307 | "JenkinsELBSecurityGroup": {
308 | "Type": "AWS::EC2::SecurityGroup",
309 | "Properties": {
310 | "GroupDescription": "SecurityGroup for Jenkins ELB",
311 | "VpcId": {
312 | "Ref": "VPC"
313 | },
314 | "SecurityGroupIngress": [
315 | {
316 | "IpProtocol": "tcp",
317 | "FromPort": "80",
318 | "ToPort": "80",
319 | "CidrIp": {
320 | "Ref": "AllowedIPRange"
321 | }
322 | }
323 | ]
324 | }
325 | },
326 | "EFSSecurityGroup": {
327 | "Type": "AWS::EC2::SecurityGroup",
328 | "Properties": {
329 | "GroupDescription": "Security group for EFS mount target",
330 | "VpcId": {
331 | "Ref": "VPC"
332 | },
333 | "SecurityGroupIngress": [
334 | {
335 | "IpProtocol": "tcp",
336 | "FromPort": "2049",
337 | "ToPort": "2049",
338 | "CidrIp": {
339 | "Ref": "VPCIPRange"
340 | }
341 | }
342 | ]
343 | }
344 | },
345 | "JenkinsEFS": {
346 | "Type": "AWS::EFS::FileSystem",
347 | "Properties": {
348 | "FileSystemTags": [
349 | {
350 | "Key": "Name",
351 | "Value": "JenkinsEFS"
352 | }
353 | ]
354 | }
355 | },
356 | "MountTarget": {
357 | "Type": "AWS::EFS::MountTarget",
358 | "Properties": {
359 | "FileSystemId": {
360 | "Ref": "JenkinsEFS"
361 | },
362 | "SubnetId": {
363 | "Ref": "Subnet"
364 | },
365 | "SecurityGroups": [
366 | {
367 | "Ref": "EFSSecurityGroup"
368 | }
369 | ]
370 | }
371 | },
372 | "JenkinsELB": {
373 | "Type": "AWS::ElasticLoadBalancing::LoadBalancer",
374 | "Properties": {
375 | "LoadBalancerName": "jenkins-elb",
376 | "Scheme": "internet-facing",
377 | "Subnets": [
378 | {
379 | "Ref": "Subnet"
380 | }
381 | ],
382 | "SecurityGroups": [
383 | {
384 | "Ref": "JenkinsELBSecurityGroup"
385 | }
386 | ],
387 | "Listeners": [
388 | {
389 | "InstancePort": "8080",
390 | "InstanceProtocol": "HTTP",
391 | "LoadBalancerPort": "80",
392 | "Protocol": "HTTP",
393 | "PolicyNames": [
394 | "JenkinsELBStickiness"
395 | ]
396 | }
397 | ],
398 | "LBCookieStickinessPolicy": [
399 | {
400 | "CookieExpirationPeriod": "3600",
401 | "PolicyName": "JenkinsELBStickiness"
402 | }
403 | ],
404 | "HealthCheck": {
405 | "HealthyThreshold": "3",
406 | "Interval": "20",
407 | "Target": "HTTP:8080/login",
408 | "Timeout": "2",
409 | "UnhealthyThreshold": "10"
410 | }
411 | }
412 | },
413 | "JenkinsCluster": {
414 | "Type": "AWS::ECS::Cluster",
415 | "Properties": {
416 | "ClusterName": "jenkins-cluster"
417 | }
418 | },
419 | "JenkinsMasterTaskDefinition": {
420 | "Type": "AWS::ECS::TaskDefinition",
421 | "Properties": {
422 | "Family": "jenkins-master",
423 | "NetworkMode": "bridge",
424 | "ContainerDefinitions": [
425 | {
426 | "Name": "jenkins-master",
427 | "Image": {
428 | "Ref": "DockerImage"
429 | },
430 | "MountPoints": [
431 | {
432 | "SourceVolume": "data-volume",
433 | "ContainerPath": "/var/jenkins_home"
434 | },
435 | {
436 | "SourceVolume": "docker-volume",
437 | "ContainerPath": "/var/run/docker.sock"
438 | }
439 | ],
440 | "Essential": true,
441 | "Cpu": 3,
442 | "MemoryReservation": 2048,
443 | "PortMappings": [
444 | {
445 | "HostPort": 8080,
446 | "ContainerPort": 8080,
447 | "Protocol": "tcp"
448 | },
449 | {
450 | "HostPort": 50000,
451 | "ContainerPort": 50000,
452 | "Protocol": "tcp"
453 | }
454 | ]
455 | }
456 | ],
457 | "Volumes": [
458 | {
459 | "Host": {
460 | "SourcePath": "/data/"
461 | },
462 | "Name": "data-volume"
463 | },
464 | {
465 | "Host": {
466 | "SourcePath": "/var/run/docker.sock"
467 | },
468 | "Name": "docker-volume"
469 | }
470 | ]
471 | }
472 | },
473 | "JenkinsECSService": {
474 | "DependsOn": ["JenkinsELB"],
475 | "Type": "AWS::ECS::Service",
476 | "Properties": {
477 | "Cluster": "jenkins-cluster",
478 | "DesiredCount": 1,
479 | "ServiceName": "jenkins-master",
480 | "TaskDefinition": {
481 | "Ref": "JenkinsMasterTaskDefinition"
482 | },
483 | "Role" : { "Ref" : "ECSServiceRole" },
484 | "LoadBalancers": [
485 | {
486 | "LoadBalancerName": "jenkins-elb",
487 | "ContainerPort": "8080",
488 | "ContainerName": "jenkins-master"
489 | }
490 | ]
491 | }
492 | },
493 | "JenkinsECSLaunchConfiguration": {
494 | "Type": "AWS::AutoScaling::LaunchConfiguration",
495 | "Properties": {
496 | "AssociatePublicIpAddress": true,
497 | "ImageId": {
498 | "Fn::FindInMap": [
499 | "RegionAmazonECSOptimizedAMIMapping",
500 | {
501 | "Ref": "AWS::Region"
502 | },
503 | "AMI"
504 | ]
505 | },
506 | "IamInstanceProfile": {
507 | "Ref": "JenkinsECSInstanceProfile"
508 | },
509 | "InstanceType": {
510 | "Ref": "InstanceType"
511 | },
512 | "KeyName": {"Fn::If": ["HasKeyName", {"Ref": "KeyName"}, {"Ref": "AWS::NoValue"}]},
513 | "SecurityGroups": [
514 | {
515 | "Ref": "JenkinsSecurityGroup"
516 | }
517 | ],
518 | "BlockDeviceMappings": [
519 | {
520 | "DeviceName": "/dev/xvdcz",
521 | "Ebs": {
522 | "VolumeSize": "24",
523 | "DeleteOnTermination": true
524 | }
525 | }
526 | ],
527 | "UserData": {
528 | "Fn::Base64": {
529 | "Fn::Join": [
530 | "",
531 | [
532 | "#!/bin/bash\n",
533 | "echo 'ECS_CLUSTER=jenkins-cluster' >> /etc/ecs/ecs.config\n",
534 | "#Mount EFS volume\n",
535 | "yum install -y nfs-utils\n",
536 | "EC2_AVAIL_ZONE=`curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone`\n",
537 | "EC2_REGION=",
538 | {
539 | "Ref": "AWS::Region"
540 | },
541 | "\n",
542 | "EFS_FILE_SYSTEM_ID=",
543 | {
544 | "Ref": "JenkinsEFS"
545 | },
546 | "\n",
547 | "EFS_PATH=$EC2_AVAIL_ZONE.$EFS_FILE_SYSTEM_ID.efs.$EC2_REGION.amazonaws.com\n",
548 | "mkdir /data\n",
549 | "mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 $EFS_PATH:/ /data\n",
550 | "#Give ownership to jenkins user\n",
551 | "chown 1000 /data\n",
552 | "chmod 777 /var/run/docker.sock\n",
553 | "if [[ ! -d /data/jobs/ ]]; then cd /data; curl -LJO https://github.com/aws-samples/amazon-eks-cdk-blue-green-cicd/raw/master/cicd/jenkins-jobs-archive.tar.gz; tar -xzvf jenkins-jobs-archive.tar.gz; cd; ls /data/jobs/; else echo 'SKIPPING DOWNLOAD...'; fi\n"
554 | ]
555 | ]
556 | }
557 | }
558 | }
559 | },
560 | "JenkinsECSAutoScaling": {
561 | "Type": "AWS::AutoScaling::AutoScalingGroup",
562 | "Properties": {
563 | "VPCZoneIdentifier": [
564 | {
565 | "Ref": "Subnet"
566 | }
567 | ],
568 | "LaunchConfigurationName": {
569 | "Ref": "JenkinsECSLaunchConfiguration"
570 | },
571 | "MinSize": "2",
572 | "MaxSize": "5",
573 | "DesiredCapacity": "2",
574 | "HealthCheckType": "EC2",
575 | "HealthCheckGracePeriod": "400",
576 | "Tags": [
577 | {
578 | "Key": "Name",
579 | "Value": "jenkins-ecs-instance",
580 | "PropagateAtLaunch": "true"
581 | }
582 | ]
583 | }
584 | },
585 |
586 | "JenkinsClusterScaleUpPolicy": {
587 | "Type" : "AWS::AutoScaling::ScalingPolicy",
588 | "Properties" : {
589 | "AdjustmentType" : "ChangeInCapacity",
590 | "AutoScalingGroupName" : { "Ref": "JenkinsECSAutoScaling" },
591 | "EstimatedInstanceWarmup" : 60,
592 | "MetricAggregationType" : "Average",
593 | "PolicyType" : "StepScaling",
594 | "StepAdjustments" : [ {
595 | "MetricIntervalLowerBound" : 0,
596 | "ScalingAdjustment" : 2
597 | }]
598 | }
599 | },
600 |
601 | "JenkinsClusterScaleUpAlarm" : {
602 | "Type" : "AWS::CloudWatch::Alarm",
603 | "Properties" : {
604 | "AlarmDescription" : "CPU utilization peaked at 70% during the last minute",
605 | "AlarmName" : "JenkinsClusterScaleUpAlarm",
606 | "AlarmActions": [ { "Ref": "JenkinsClusterScaleUpPolicy" } ],
607 | "Dimensions" : [{
608 | "Name": "ClusterName",
609 | "Value": "jenkins-cluster"
610 | }],
611 | "MetricName" : "CPUReservation",
612 | "Namespace" : "AWS/ECS",
613 | "ComparisonOperator" : "GreaterThanOrEqualToThreshold",
614 | "Statistic" : "Maximum",
615 | "Threshold" : 70,
616 | "Period" : 60,
617 | "EvaluationPeriods": 1,
618 | "TreatMissingData" : "notBreaching"
619 | }
620 | },
621 |
622 | "JenkinsClusterScaleDownPolicy": {
623 | "Type" : "AWS::AutoScaling::ScalingPolicy",
624 | "Properties" : {
625 | "AdjustmentType" : "PercentChangeInCapacity",
626 | "AutoScalingGroupName" : { "Ref": "JenkinsECSAutoScaling" },
627 | "Cooldown" : "120",
628 | "ScalingAdjustment" : "-50"
629 | }
630 | },
631 |
632 | "JenkinsClusterScaleDownAlarm" : {
633 | "Type" : "AWS::CloudWatch::Alarm",
634 | "Properties" : {
635 | "AlarmDescription" : "CPU utilization is under 50% for the last 10 min (change 10 min to 45 min for prod use as you pay by the hour )",
636 | "AlarmName" : "JenkinsClusterScaleDownAlarm",
637 | "AlarmActions": [ { "Ref": "JenkinsClusterScaleDownPolicy" } ],
638 | "Dimensions" : [{
639 | "Name": "ClusterName",
640 | "Value": "jenkins-cluster"
641 | }],
642 | "MetricName" : "CPUReservation",
643 | "Namespace" : "AWS/ECS",
644 | "ComparisonOperator" : "LessThanThreshold",
645 | "Statistic" : "Maximum",
646 | "Threshold" : 50,
647 | "Period" : 600,
648 | "EvaluationPeriods": 1,
649 | "TreatMissingData" : "notBreaching"
650 | }
651 | }
652 |
653 | },
654 | "Outputs" : {
655 | "JenkinsELB" : {
656 | "Description": "Jenkins URL",
657 | "Value" : {"Fn::Join": ["", ["http://", { "Fn::GetAtt" : [ "JenkinsELB", "DNSName" ]}] ]}
658 | }
659 | }
660 | }
661 |
--------------------------------------------------------------------------------
/cicd/jenkins-ecs-workshop3.json:
--------------------------------------------------------------------------------
1 | {
2 | "AWSTemplateFormatVersion": "2010-09-09",
3 | "Description": "Cloudformation stack for Jenkins ECS",
4 | "Parameters": {
5 | "KeyName": {
6 | "Description": "Optional: the EC2 Key Pair to allow SSH access to the instances",
7 | "Type": "String",
8 | "Default": ""
9 | },
10 | "AllowedIPRange": {
11 | "Description": "The public IP address range that can be used to connect to the instances",
12 | "Type": "String",
13 | "MinLength": "9",
14 | "MaxLength": "18",
15 | "Default": "0.0.0.0/0",
16 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
17 | "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
18 | },
19 | "VPCIPRange": {
20 | "Description": "The private IP address range for allocating IPs within the VPC.",
21 | "Type": "String",
22 | "MinLength": "9",
23 | "MaxLength": "18",
24 | "Default": "10.0.0.0/16",
25 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
26 | "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
27 | },
28 | "InstanceType": {
29 | "Description": "EC2 instance type",
30 | "Type": "String",
31 | "Default": "t2.medium",
32 | "AllowedValues": [
33 | "t2.micro",
34 | "t2.small",
35 | "t2.medium"
36 | ],
37 | "ConstraintDescription": "Must be a valid EC2 instance type."
38 | },
39 | "DockerImage": {
40 | "Description": "The docker image to use for Jenkins",
41 | "Type": "String",
42 | "Default": "nikunjv/jenkins:v4"
43 | }
44 | },
45 | "Mappings": {
46 | "RegionAmazonECSOptimizedAMIMapping": {
47 | "us-east-2": {
48 | "AMI": "ami-0c0415cdff14e2a4a"
49 | },
50 | "us-east-1": {
51 | "AMI": "ami-098616968d61e549e"
52 | },
53 | "us-west-2": {
54 | "AMI": "ami-014a2e30da708ee8b"
55 | },
56 | "us-west-1": {
57 | "AMI": "ami-00271233a1ebb9161"
58 | },
59 | "eu-west-2": {
60 | "AMI": "ami-02254c861b8371869"
61 | },
62 | "eu-west-1": {
63 | "AMI": "ami-0bb01c7d2705a4800"
64 | },
65 | "eu-central-1": {
66 | "AMI": "ami-01b521fb7540d9996"
67 | },
68 | "ap-northeast-1": {
69 | "AMI": "ami-0763fff45988661c8"
70 | },
71 | "ap-southeast-2": {
72 | "AMI": "ami-0a7c4f7f17d3eecbc"
73 | },
74 | "ap-southeast-1": {
75 | "AMI": "ami-0bd1daf5da8a9a903"
76 | },
77 | "ca-central-1": {
78 | "AMI": "ami-092262e997a1ab27b"
79 | }
80 | }
81 | },
82 | "Conditions": {
83 | "HasKeyName": {"Fn::Not": [{"Fn::Equals": ["", {"Ref": "KeyName"}]}]}
84 | },
85 | "Resources": {
86 | "VPC": {
87 | "Type": "AWS::EC2::VPC",
88 | "Properties": {
89 | "EnableDnsSupport": "true",
90 | "EnableDnsHostnames": "true",
91 | "CidrBlock": {"Ref": "VPCIPRange"},
92 | "Tags": [
93 | {
94 | "Key": "Application",
95 | "Value": {
96 | "Ref": "AWS::StackId"
97 | }
98 | }
99 | ]
100 | }
101 | },
102 | "InternetGateway": {
103 | "Type": "AWS::EC2::InternetGateway",
104 | "Properties": {
105 | "Tags": [
106 | {
107 | "Key": "Application",
108 | "Value": {
109 | "Ref": "AWS::StackName"
110 | }
111 | },
112 | {
113 | "Key": "Network",
114 | "Value": "Public"
115 | }
116 | ]
117 | }
118 | },
119 | "GatewayToInternet": {
120 | "Type": "AWS::EC2::VPCGatewayAttachment",
121 | "Properties": {
122 | "VpcId": {
123 | "Ref": "VPC"
124 | },
125 | "InternetGatewayId": {
126 | "Ref": "InternetGateway"
127 | }
128 | }
129 | },
130 | "RouteTable": {
131 | "Type": "AWS::EC2::RouteTable",
132 | "Properties": {
133 | "VpcId": {
134 | "Ref": "VPC"
135 | }
136 | }
137 | },
138 | "SubnetRouteTableAssoc": {
139 | "Type": "AWS::EC2::SubnetRouteTableAssociation",
140 | "Properties": {
141 | "RouteTableId": {
142 | "Ref": "RouteTable"
143 | },
144 | "SubnetId": {
145 | "Ref": "Subnet"
146 | }
147 | }
148 | },
149 | "InternetGatewayRoute": {
150 | "Type": "AWS::EC2::Route",
151 | "Properties": {
152 | "DestinationCidrBlock": "0.0.0.0/0",
153 | "RouteTableId": {
154 | "Ref": "RouteTable"
155 | },
156 | "GatewayId": {
157 | "Ref": "InternetGateway"
158 | }
159 | }
160 | },
161 | "Subnet": {
162 | "Type": "AWS::EC2::Subnet",
163 | "Properties": {
164 | "VpcId": {
165 | "Ref": "VPC"
166 | },
167 | "CidrBlock": {"Ref": "VPCIPRange"},
168 | "Tags": [
169 | {
170 | "Key": "Application",
171 | "Value": {
172 | "Ref": "AWS::StackId"
173 | }
174 | }
175 | ]
176 | }
177 | },
178 | "ECSServiceRole":{
179 | "Type":"AWS::IAM::Role",
180 | "Properties":{
181 | "AssumeRolePolicyDocument":{
182 | "Statement":[
183 | {
184 | "Effect":"Allow",
185 | "Principal":{
186 | "Service":[
187 | "ecs.amazonaws.com"
188 | ]
189 | },
190 | "Action":[
191 | "sts:AssumeRole"
192 | ]
193 | }
194 | ]
195 | },
196 | "Path":"/",
197 | "Policies":[
198 | {
199 | "PolicyName":"ecs-service",
200 | "PolicyDocument":{
201 | "Statement":[
202 | {
203 | "Effect":"Allow",
204 | "Action":[
205 | "elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
206 | "elasticloadbalancing:DeregisterTargets",
207 | "elasticloadbalancing:Describe*",
208 | "elasticloadbalancing:RegisterInstancesWithLoadBalancer",
209 | "elasticloadbalancing:RegisterTargets",
210 | "ec2:Describe*",
211 | "ec2:AuthorizeSecurityGroupIngress"
212 | ],
213 | "Resource":"*"
214 | }
215 | ]
216 | }
217 | }
218 | ]
219 | }
220 | },
221 | "EC2Role":{
222 | "Type":"AWS::IAM::Role",
223 | "Properties":{
224 | "AssumeRolePolicyDocument":{
225 | "Statement":[
226 | {
227 | "Effect":"Allow",
228 | "Principal":{
229 | "Service":[
230 | "ec2.amazonaws.com"
231 | ]
232 | },
233 | "Action":[
234 | "sts:AssumeRole"
235 | ]
236 | }
237 | ]
238 | },
239 | "Path":"/",
240 | "Policies":[
241 | {
242 | "PolicyName":"ecs-service",
243 | "PolicyDocument":{
244 | "Statement":[
245 | {
246 | "Effect":"Allow",
247 | "Action":[
248 | "ecs:*",
249 | "elasticloadbalancing:Describe*",
250 | "logs:CreateLogStream",
251 | "logs:PutLogEvents"
252 | ],
253 | "Resource":"*"
254 | }
255 | ]
256 | }
257 | }
258 | ]
259 | }
260 | },
261 | "JenkinsECSInstanceProfile": {
262 | "Type": "AWS::IAM::InstanceProfile",
263 | "Properties": {
264 | "Path": "/",
265 | "Roles": [
266 | {
267 | "Ref": "EC2Role"
268 | }
269 | ]
270 | }
271 | },
272 | "JenkinsSecurityGroup": {
273 | "Type": "AWS::EC2::SecurityGroup",
274 | "Properties": {
275 | "GroupDescription": "SecurityGroup for Jenkins instances: manager and workers",
276 | "VpcId": {
277 | "Ref": "VPC"
278 | },
279 | "SecurityGroupIngress": [
280 | {
281 | "IpProtocol": "tcp",
282 | "FromPort": "22",
283 | "ToPort": "22",
284 | "CidrIp": {
285 | "Ref": "AllowedIPRange"
286 | }
287 | },
288 | {
289 | "IpProtocol": "tcp",
290 | "FromPort": "8080",
291 | "ToPort": "8080",
292 | "CidrIp": {
293 | "Ref": "VPCIPRange"
294 | }
295 | },
296 | {
297 | "IpProtocol": "tcp",
298 | "FromPort": "50000",
299 | "ToPort": "50000",
300 | "CidrIp": {
301 | "Ref": "VPCIPRange"
302 | }
303 | }
304 | ]
305 | }
306 | },
307 | "JenkinsELBSecurityGroup": {
308 | "Type": "AWS::EC2::SecurityGroup",
309 | "Properties": {
310 | "GroupDescription": "SecurityGroup for Jenkins ELB",
311 | "VpcId": {
312 | "Ref": "VPC"
313 | },
314 | "SecurityGroupIngress": [
315 | {
316 | "IpProtocol": "tcp",
317 | "FromPort": "80",
318 | "ToPort": "80",
319 | "CidrIp": {
320 | "Ref": "AllowedIPRange"
321 | }
322 | }
323 | ]
324 | }
325 | },
326 | "EFSSecurityGroup": {
327 | "Type": "AWS::EC2::SecurityGroup",
328 | "Properties": {
329 | "GroupDescription": "Security group for EFS mount target",
330 | "VpcId": {
331 | "Ref": "VPC"
332 | },
333 | "SecurityGroupIngress": [
334 | {
335 | "IpProtocol": "tcp",
336 | "FromPort": "2049",
337 | "ToPort": "2049",
338 | "CidrIp": {
339 | "Ref": "VPCIPRange"
340 | }
341 | }
342 | ]
343 | }
344 | },
345 | "JenkinsEFS": {
346 | "Type": "AWS::EFS::FileSystem",
347 | "Properties": {
348 | "FileSystemTags": [
349 | {
350 | "Key": "Name",
351 | "Value": "JenkinsEFS"
352 | }
353 | ]
354 | }
355 | },
356 | "MountTarget": {
357 | "Type": "AWS::EFS::MountTarget",
358 | "Properties": {
359 | "FileSystemId": {
360 | "Ref": "JenkinsEFS"
361 | },
362 | "SubnetId": {
363 | "Ref": "Subnet"
364 | },
365 | "SecurityGroups": [
366 | {
367 | "Ref": "EFSSecurityGroup"
368 | }
369 | ]
370 | }
371 | },
372 | "JenkinsELB": {
373 | "Type": "AWS::ElasticLoadBalancing::LoadBalancer",
374 | "Properties": {
375 | "LoadBalancerName": "jenkins-elb",
376 | "Scheme": "internet-facing",
377 | "Subnets": [
378 | {
379 | "Ref": "Subnet"
380 | }
381 | ],
382 | "SecurityGroups": [
383 | {
384 | "Ref": "JenkinsELBSecurityGroup"
385 | }
386 | ],
387 | "Listeners": [
388 | {
389 | "InstancePort": "8080",
390 | "InstanceProtocol": "HTTP",
391 | "LoadBalancerPort": "80",
392 | "Protocol": "HTTP",
393 | "PolicyNames": [
394 | "JenkinsELBStickiness"
395 | ]
396 | }
397 | ],
398 | "LBCookieStickinessPolicy": [
399 | {
400 | "CookieExpirationPeriod": "3600",
401 | "PolicyName": "JenkinsELBStickiness"
402 | }
403 | ],
404 | "HealthCheck": {
405 | "HealthyThreshold": "3",
406 | "Interval": "20",
407 | "Target": "HTTP:8080/login",
408 | "Timeout": "2",
409 | "UnhealthyThreshold": "10"
410 | }
411 | }
412 | },
413 | "JenkinsCluster": {
414 | "Type": "AWS::ECS::Cluster",
415 | "Properties": {
416 | "ClusterName": "jenkins-cluster"
417 | }
418 | },
419 | "JenkinsManagerTaskDefinition": {
420 | "Type": "AWS::ECS::TaskDefinition",
421 | "Properties": {
422 | "Family": "jenkins-manager",
423 | "NetworkMode": "bridge",
424 | "ContainerDefinitions": [
425 | {
426 | "Name": "jenkins-manager",
427 | "Image": {
428 | "Ref": "DockerImage"
429 | },
430 | "MountPoints": [
431 | {
432 | "SourceVolume": "data-volume",
433 | "ContainerPath": "/var/jenkins_home"
434 | },
435 | {
436 | "SourceVolume": "docker-volume",
437 | "ContainerPath": "/var/run/docker.sock"
438 | }
439 | ],
440 | "Essential": true,
441 | "Cpu": 3,
442 | "MemoryReservation": 2048,
443 | "PortMappings": [
444 | {
445 | "HostPort": 8080,
446 | "ContainerPort": 8080,
447 | "Protocol": "tcp"
448 | },
449 | {
450 | "HostPort": 50000,
451 | "ContainerPort": 50000,
452 | "Protocol": "tcp"
453 | }
454 | ]
455 | }
456 | ],
457 | "Volumes": [
458 | {
459 | "Host": {
460 | "SourcePath": "/data/"
461 | },
462 | "Name": "data-volume"
463 | },
464 | {
465 | "Host": {
466 | "SourcePath": "/var/run/docker.sock"
467 | },
468 | "Name": "docker-volume"
469 | }
470 | ]
471 | }
472 | },
473 | "JenkinsECSService": {
474 | "DependsOn": ["JenkinsELB"],
475 | "Type": "AWS::ECS::Service",
476 | "Properties": {
477 | "Cluster": "jenkins-cluster",
478 | "DesiredCount": 1,
479 | "ServiceName": "jenkins-manager",
480 | "TaskDefinition": {
481 | "Ref": "JenkinsManagerTaskDefinition"
482 | },
483 | "Role" : { "Ref" : "ECSServiceRole" },
484 | "LoadBalancers": [
485 | {
486 | "LoadBalancerName": "jenkins-elb",
487 | "ContainerPort": "8080",
488 | "ContainerName": "jenkins-manager"
489 | }
490 | ]
491 | }
492 | },
493 | "JenkinsECSLaunchConfiguration": {
494 | "Type": "AWS::AutoScaling::LaunchConfiguration",
495 | "Properties": {
496 | "AssociatePublicIpAddress": true,
497 | "ImageId": {
498 | "Fn::FindInMap": [
499 | "RegionAmazonECSOptimizedAMIMapping",
500 | {
501 | "Ref": "AWS::Region"
502 | },
503 | "AMI"
504 | ]
505 | },
506 | "IamInstanceProfile": {
507 | "Ref": "JenkinsECSInstanceProfile"
508 | },
509 | "InstanceType": {
510 | "Ref": "InstanceType"
511 | },
512 | "KeyName": {"Fn::If": ["HasKeyName", {"Ref": "KeyName"}, {"Ref": "AWS::NoValue"}]},
513 | "SecurityGroups": [
514 | {
515 | "Ref": "JenkinsSecurityGroup"
516 | }
517 | ],
518 | "BlockDeviceMappings": [
519 | {
520 | "DeviceName": "/dev/xvdcz",
521 | "Ebs": {
522 | "VolumeSize": "24",
523 | "DeleteOnTermination": true
524 | }
525 | }
526 | ],
527 | "UserData": {
528 | "Fn::Base64": {
529 | "Fn::Join": [
530 | "",
531 | [
532 | "#!/bin/bash\n",
533 | "echo 'ECS_CLUSTER=jenkins-cluster' >> /etc/ecs/ecs.config\n",
534 | "#Mount EFS volume\n",
535 | "yum install -y nfs-utils\n",
536 | "EC2_AVAIL_ZONE=`curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone`\n",
537 | "EC2_REGION=",
538 | {
539 | "Ref": "AWS::Region"
540 | },
541 | "\n",
542 | "EFS_FILE_SYSTEM_ID=",
543 | {
544 | "Ref": "JenkinsEFS"
545 | },
546 | "\n",
547 | "EFS_PATH=$EC2_AVAIL_ZONE.$EFS_FILE_SYSTEM_ID.efs.$EC2_REGION.amazonaws.com\n",
548 | "mkdir /data\n",
549 | "mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 $EFS_PATH:/ /data\n",
550 | "#Give ownership to jenkins user\n",
551 | "chown 1000 /data\n",
552 | "chmod 777 /var/run/docker.sock\n",
553 | "if [[ ! -d /data/jobs/ ]]; then cd /data; curl -LJO https://github.com/aws-samples/amazon-eks-cdk-blue-green-cicd/raw/master/cicd/jenkins-jobs-archive3.tar.gz; tar -xzvf jenkins-jobs-archive3.tar.gz; cd; ls /data/jobs/; else echo 'SKIPPING DOWNLOAD...'; fi\n",
554 | "#Give ownership to jenkins user\n",
555 | "chown -R 1000:1000 /data\n",
556 | "ls -altr /data"
557 | ]
558 | ]
559 | }
560 | }
561 | }
562 | },
563 | "JenkinsECSAutoScaling": {
564 | "Type": "AWS::AutoScaling::AutoScalingGroup",
565 | "Properties": {
566 | "VPCZoneIdentifier": [
567 | {
568 | "Ref": "Subnet"
569 | }
570 | ],
571 | "LaunchConfigurationName": {
572 | "Ref": "JenkinsECSLaunchConfiguration"
573 | },
574 | "MinSize": "2",
575 | "MaxSize": "5",
576 | "DesiredCapacity": "2",
577 | "HealthCheckType": "EC2",
578 | "HealthCheckGracePeriod": "400",
579 | "Tags": [
580 | {
581 | "Key": "Name",
582 | "Value": "jenkins-ecs-instance",
583 | "PropagateAtLaunch": "true"
584 | }
585 | ]
586 | }
587 | },
588 |
589 | "JenkinsClusterScaleUpPolicy": {
590 | "Type" : "AWS::AutoScaling::ScalingPolicy",
591 | "Properties" : {
592 | "AdjustmentType" : "ChangeInCapacity",
593 | "AutoScalingGroupName" : { "Ref": "JenkinsECSAutoScaling" },
594 | "EstimatedInstanceWarmup" : 60,
595 | "MetricAggregationType" : "Average",
596 | "PolicyType" : "StepScaling",
597 | "StepAdjustments" : [ {
598 | "MetricIntervalLowerBound" : 0,
599 | "ScalingAdjustment" : 2
600 | }]
601 | }
602 | },
603 |
604 | "JenkinsClusterScaleUpAlarm" : {
605 | "Type" : "AWS::CloudWatch::Alarm",
606 | "Properties" : {
607 | "AlarmDescription" : "CPU utilization peaked at 70% during the last minute",
608 | "AlarmName" : "JenkinsClusterScaleUpAlarm",
609 | "AlarmActions": [ { "Ref": "JenkinsClusterScaleUpPolicy" } ],
610 | "Dimensions" : [{
611 | "Name": "ClusterName",
612 | "Value": "jenkins-cluster"
613 | }],
614 | "MetricName" : "CPUReservation",
615 | "Namespace" : "AWS/ECS",
616 | "ComparisonOperator" : "GreaterThanOrEqualToThreshold",
617 | "Statistic" : "Maximum",
618 | "Threshold" : 70,
619 | "Period" : 60,
620 | "EvaluationPeriods": 1,
621 | "TreatMissingData" : "notBreaching"
622 | }
623 | },
624 |
625 | "JenkinsClusterScaleDownPolicy": {
626 | "Type" : "AWS::AutoScaling::ScalingPolicy",
627 | "Properties" : {
628 | "AdjustmentType" : "PercentChangeInCapacity",
629 | "AutoScalingGroupName" : { "Ref": "JenkinsECSAutoScaling" },
630 | "Cooldown" : "120",
631 | "ScalingAdjustment" : "-50"
632 | }
633 | },
634 |
635 | "JenkinsClusterScaleDownAlarm" : {
636 | "Type" : "AWS::CloudWatch::Alarm",
637 | "Properties" : {
638 | "AlarmDescription" : "CPU utilization is under 50% for the last 10 min (change 10 min to 45 min for prod use as you pay by the hour )",
639 | "AlarmName" : "JenkinsClusterScaleDownAlarm",
640 | "AlarmActions": [ { "Ref": "JenkinsClusterScaleDownPolicy" } ],
641 | "Dimensions" : [{
642 | "Name": "ClusterName",
643 | "Value": "jenkins-cluster"
644 | }],
645 | "MetricName" : "CPUReservation",
646 | "Namespace" : "AWS/ECS",
647 | "ComparisonOperator" : "LessThanThreshold",
648 | "Statistic" : "Maximum",
649 | "Threshold" : 50,
650 | "Period" : 600,
651 | "EvaluationPeriods": 1,
652 | "TreatMissingData" : "notBreaching"
653 | }
654 | }
655 |
656 | },
657 | "Outputs" : {
658 | "JenkinsELB" : {
659 | "Description": "Jenkins URL",
660 | "Value" : {"Fn::Join": ["", ["http://", { "Fn::GetAtt" : [ "JenkinsELB", "DNSName" ]}] ]}
661 | }
662 | }
663 | }
664 |
--------------------------------------------------------------------------------
/cicd/jenkins-jobs-archive.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/cicd/jenkins-jobs-archive.tar.gz
--------------------------------------------------------------------------------
/cicd/jenkins-jobs-archive3.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/cicd/jenkins-jobs-archive3.tar.gz
--------------------------------------------------------------------------------
/dockerAssets.d/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM public.ecr.aws/p8v8e7e5/myartifacts:alpine-jan2021
2 |
3 |
4 | ENV KUBECONFIG /home/kubectl/.kube/kubeconfig
5 | ENV HOME /home/kubectl
6 | # ENV KUBECONFIG /root/.kube/kubeconfig
7 |
8 |
9 | RUN \
10 | mkdir /root/bin /aws; \
11 | apk -Uuv add groff less bash python3 py-pip jq curl docker && \
12 | pip3 install --upgrade pip; \
13 | pip3 install awscli && \
14 | rm /var/cache/apk/* && \
15 | # Create non-root user (with a randomly chosen UID/GUI).
16 | adduser kubectl -Du 5566
17 |
18 | ADD https://s3.us-west-2.amazonaws.com/amazon-eks/1.22.6/2022-03-09/bin/linux/amd64/kubectl /usr/local/bin/kubectl
19 | #COPY kubectl /usr/local/bin/kubectl
20 |
21 | WORKDIR $HOME
22 |
23 | COPY entrypoint.sh /usr/local/bin/entrypoint.sh
24 |
25 | RUN chmod a+x /usr/local/bin/kubectl /usr/local/bin/entrypoint.sh
26 |
27 | VOLUME /var/lib/docker
28 |
29 | # USER kubectl
30 | ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
31 |
--------------------------------------------------------------------------------
/dockerAssets.d/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | # export PATH=$PATH:/root/bin
5 | HOME=/home/kubectl
6 |
7 | export KUBECONFIG=$HOME/.kube/kubeconfig
8 |
9 | start_dockerd() {
10 | /usr/bin/dockerd \
11 | --host=unix:///var/run/docker.sock \
12 | --host=tcp://127.0.0.1:2375 \
13 | --storage-driver=overlay &>/var/log/docker.log &
14 | tries=0
15 | d_timeout=60
16 | until docker info >/dev/null 2>&1
17 | do
18 | if [ "$tries" -gt "$d_timeout" ]; then
19 | cat /var/log/docker.log
20 | echo 'Timed out trying to connect to internal docker host.' >&2
21 | exit 1
22 | fi
23 | tries=$(( $tries + 1 ))
24 | sleep 1
25 | done
26 | }
27 |
28 |
29 | if [[ ! -z ${CODEBUILD_BUILD_ID} ]]; then
30 | # in AWS CodeBuild
31 | echo "found myself in AWS CodeBuild, starting dockerd..."
32 | start_dockerd
33 | fi
34 |
35 |
36 | if [[ ! -z ${AWS_REGION} ]]; then
37 | region=$AWS_REGION
38 | echo "[INFO] region=$AWS_REGION"
39 | else
40 | echo "REGION not defined, trying to lookup from EC2 metadata..."
41 | region=$(curl -s http://169.254.169.254/latest/dynamic/instance-identity/document | jq .region -r)
42 | fi
43 |
44 | # export AWS_DEFAULT_REGION=${REGION-${CODEBUILD_AGENT_ENV_CODEBUILD_REGION-$region}}
45 | export AWS_DEFAULT_REGION=$region
46 |
47 | CLUSTER_NAME=${CLUSTER_NAME-default}
48 |
49 | update_kubeconfig(){
50 | if [[ -n ${EKS_ROLE_ARN} ]]; then
51 | echo "[INFO] got EKS_ROLE_ARN=${EKS_ROLE_ARN}, updating kubeconfig with this role"
52 | aws eks update-kubeconfig --name $CLUSTER_NAME --kubeconfig $KUBECONFIG --role-arn "${EKS_ROLE_ARN}"
53 | else
54 | aws eks update-kubeconfig --name $CLUSTER_NAME --kubeconfig $KUBECONFIG
55 | fi
56 | }
57 |
58 | update_kubeconfig
59 | exec "$@"
60 |
--------------------------------------------------------------------------------
/flask-docker-app/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/flask-docker-app/.DS_Store
--------------------------------------------------------------------------------
/flask-docker-app/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM public.ecr.aws/p8v8e7e5/myartifacts:alpine-3.8
2 | RUN apk add python3 py-pip && \
3 | python3 -m ensurepip && \
4 | pip install --upgrade pip && \
5 | pip install flask
6 |
7 | ENV FLASK_APP app.py
8 | ENV PLATFORM 'Amazon EKS'
9 |
10 | WORKDIR /app
11 | COPY . /app/
12 |
13 | CMD ["python", "app.py"]
14 |
--------------------------------------------------------------------------------
/flask-docker-app/app.py:
--------------------------------------------------------------------------------
1 | # from flask import Flask, escape, request, render_template
2 | import flask
3 | import datetime
4 | import platform
5 | import os
6 |
7 | app = flask.Flask(__name__)
8 |
9 |
10 | @app.route('/')
11 | def hello():
12 | name = flask.request.args.get("name", "Flask-demo")
13 | time = datetime.datetime.now()
14 | python_version = platform.python_version()
15 | aws_platform = os.environ.get('PLATFORM', 'Amazon Web Services')
16 | return flask.render_template('hello.html',
17 | platform=aws_platform,
18 | flask_version=flask.__version__,
19 | python_version=python_version,
20 | flask_url='https://palletsprojects.com/p/flask/',
21 | time=time,
22 | name=name)
23 |
24 |
25 | if __name__ == '__main__':
26 | app.run(
27 | debug=True,
28 | host='0.0.0.0',
29 | port=5000
30 | )
31 |
--------------------------------------------------------------------------------
/flask-docker-app/k8s/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/flask-docker-app/k8s/.DS_Store
--------------------------------------------------------------------------------
/flask-docker-app/k8s/alb-ingress-controller.yaml:
--------------------------------------------------------------------------------
1 | # Application Load Balancer (ALB) Ingress Controller Deployment Manifest.
2 | # This manifest details sensible defaults for deploying an ALB Ingress Controller.
3 | # GitHub: https://github.com/kubernetes-sigs/aws-alb-ingress-controller
4 | apiVersion: apps/v1
5 | kind: Deployment
6 | metadata:
7 | labels:
8 | app.kubernetes.io/name: alb-ingress-controller
9 | name: alb-ingress-controller
10 | # Namespace the ALB Ingress Controller should run in. Does not impact which
11 | # namespaces it's able to resolve ingress resource for. For limiting ingress
12 | # namespace scope, see --watch-namespace.
13 | namespace: kube-system
14 | spec:
15 | selector:
16 | matchLabels:
17 | app.kubernetes.io/name: alb-ingress-controller
18 | template:
19 | metadata:
20 | labels:
21 | app.kubernetes.io/name: alb-ingress-controller
22 | spec:
23 | containers:
24 | - name: alb-ingress-controller
25 | args:
26 | # Limit the namespace where this ALB Ingress Controller deployment will
27 | # resolve ingress resources. If left commented, all namespaces are used.
28 | # - --watch-namespace=your-k8s-namespace
29 |
30 | # Setting the ingress-class flag below ensures that only ingress resources with the
31 | # annotation kubernetes.io/ingress.class: "alb" are respected by the controller. You may
32 | # choose any class you'd like for this controller to respect.
33 | - --ingress-class=alb
34 |
35 | # REQUIRED
36 | # Name of your cluster. Used when naming resources created
37 | # by the ALB Ingress Controller, providing distinction between
38 | # clusters.
39 | - --cluster-name=devCluster
40 |
41 | # AWS VPC ID this ingress controller will use to create AWS resources.
42 | # If unspecified, it will be discovered from ec2metadata.
43 | # - --aws-vpc-id=vpc-xxxxxx
44 |
45 | # AWS region this ingress controller will operate in.
46 | # If unspecified, it will be discovered from ec2metadata.
47 | # List of regions: http://docs.aws.amazon.com/general/latest/gr/rande.html#vpc_region
48 | # - --aws-region=us-west-1
49 |
50 | # Enables logging on all outbound requests sent to the AWS API.
51 | # If logging is desired, set to true.
52 | # - --aws-api-debug
53 | # Maximum number of times to retry the aws calls.
54 | # defaults to 10.
55 | # - --aws-max-retries=10
56 | # env:
57 | # AWS key id for authenticating with the AWS API.
58 | # This is only here for examples. It's recommended you instead use
59 | # a project like kube2iam for granting access.
60 | #- name: AWS_ACCESS_KEY_ID
61 | # value: KEYVALUE
62 |
63 | # AWS key secret for authenticating with the AWS API.
64 | # This is only here for examples. It's recommended you instead use
65 | # a project like kube2iam for granting access.
66 | #- name: AWS_SECRET_ACCESS_KEY
67 | # value: SECRETVALUE
68 | # Repository location of the ALB Ingress Controller.
69 | image: docker.io/amazon/aws-alb-ingress-controller:v1.1.5
70 | serviceAccountName: alb-ingress-controller
71 |
--------------------------------------------------------------------------------
/flask-docker-app/k8s/flask-ALB-namespace.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Namespace
3 | metadata:
4 | name: "flask-alb"
5 |
--------------------------------------------------------------------------------
/flask-docker-app/k8s/flask.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: flask-svc
5 | spec:
6 | selector:
7 | app: flask
8 | ports:
9 | - name: web
10 | port: 80
11 | targetPort: 5000
12 | type: LoadBalancer
13 | ---
14 | apiVersion: apps/v1beta1
15 | kind: Deployment
16 | metadata:
17 | labels:
18 | run: flask
19 | name: flask
20 | spec:
21 | replicas: 1
22 | template:
23 | metadata:
24 | labels:
25 | app: flask
26 | spec:
27 | containers:
28 | - name: flask
29 | image: nikunjv/flask-image:blue
30 | ports:
31 | - containerPort: 5000
32 | # command:
33 | # - "sh"
34 | # - "-c"
35 | # - "yum install -y python3-pip && tail -f /var/log/yum.log "
36 | resources:
37 | limits:
38 | memory: "500Mi"
39 | cpu: "0.25"
40 |
--------------------------------------------------------------------------------
/flask-docker-app/k8s/flaskALBBlue.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: "flask-svc-alb-blue"
5 | namespace: "flask-alb"
6 | spec:
7 | selector:
8 | app: "flask-deploy-alb-blue"
9 | type: NodePort
10 | ports:
11 | - name: web
12 | port: 80
13 | targetPort: 5000
14 | ---
15 | apiVersion: apps/v1
16 | kind: Deployment
17 | metadata:
18 | labels:
19 | run: "flask"
20 | name: "flask-deploy-alb-blue"
21 | namespace: "flask-alb"
22 | spec:
23 | selector:
24 | matchLabels:
25 | app: "flask-deploy-alb-blue"
26 | replicas: 1
27 | template:
28 | metadata:
29 | labels:
30 | app: "flask-deploy-alb-blue"
31 | spec:
32 | containers:
33 | - name: "flask"
34 | image: public.ecr.aws/p8v8e7e5/myartifacts:flask-image-grey
35 | ports:
36 | - containerPort: 5000
37 | # command:
38 | # - "sh"
39 | # - "-c"
40 | # - "yum install -y python3-pip && tail -f /var/log/yum.log "
41 | resources:
42 | limits:
43 | memory: "500Mi"
44 | cpu: "0.25"
45 |
--------------------------------------------------------------------------------
/flask-docker-app/k8s/flaskALBGreen.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: "flask-svc-alb-green"
5 | namespace: "flask-alb"
6 | spec:
7 | selector:
8 | app: "flask-deploy-alb-green"
9 | type: NodePort
10 | ports:
11 | - name: web
12 | port: 8080
13 | targetPort: 5000
14 | ---
15 | apiVersion: apps/v1
16 | kind: Deployment
17 | metadata:
18 | labels:
19 | run: "flask"
20 | name: "flask-deploy-alb-green"
21 | namespace: "flask-alb"
22 | spec:
23 | selector:
24 | matchLabels:
25 | app: "flask-deploy-alb-green"
26 | replicas: 1
27 | template:
28 | metadata:
29 | labels:
30 | app: "flask-deploy-alb-green"
31 | spec:
32 | containers:
33 | - name: "flask"
34 | image: public.ecr.aws/p8v8e7e5/myartifacts:flask-image-grey
35 | ports:
36 | - containerPort: 5000
37 | # command:
38 | # - "sh"
39 | # - "-c"
40 | # - "yum install -y python3-pip && tail -f /var/log/yum.log "
41 | resources:
42 | limits:
43 | memory: "500Mi"
44 | cpu: "0.25"
45 |
--------------------------------------------------------------------------------
/flask-docker-app/k8s/flaskALBIngress_query.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: extensions/v1beta1
2 | kind: Ingress
3 | metadata:
4 | name: alb-ingress
5 | namespace: "flask-alb"
6 | annotations:
7 | kubernetes.io/ingress.class: alb
8 | alb.ingress.kubernetes.io/scheme: internet-facing
9 | alb.ingress.kubernetes.io/subnets: public-subnets
10 | alb.ingress.kubernetes.io/security-groups: sec-grp
11 | alb.ingress.kubernetes.io/conditions.flask-svc-alb-green: '[{"Field":"query-string","QueryStringConfig":{"Values":[{"Key":"group","Value":"green"}]}}]'
12 | alb.ingress.kubernetes.io/conditions.forward-multiple-tg: '[{"Field":"query-string","QueryStringConfig":{"Values":[{"Key":"group","Value":"blue"}]}}]'
13 | alb.ingress.kubernetes.io/actions.forward-multiple-tg: '{"Type":"forward","ForwardConfig":{"TargetGroups":[{"ServiceName":"flask-svc-alb-blue","ServicePort":"80","Weight":100},{"ServiceName":"flask-svc-alb-green","ServicePort":"8080","Weight":0}]}}'
14 |
15 | labels:
16 | app: flask-ingress
17 | spec:
18 | rules:
19 | - http:
20 | paths:
21 | - backend:
22 | serviceName: flask-svc-alb-green
23 | servicePort: 8080
24 | - backend:
25 | serviceName: forward-multiple-tg
26 | servicePort: use-annotation
27 | - path: /*
28 | backend:
29 | serviceName: flask-svc-alb-blue
30 | servicePort: 80
31 |
--------------------------------------------------------------------------------
/flask-docker-app/k8s/flaskALBIngress_query2.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: extensions/v1beta1
2 | kind: Ingress
3 | metadata:
4 | name: alb-ingress
5 | namespace: "flask-alb"
6 | annotations:
7 | kubernetes.io/ingress.class: alb
8 | alb.ingress.kubernetes.io/scheme: internet-facing
9 | alb.ingress.kubernetes.io/subnets: public-subnets
10 | alb.ingress.kubernetes.io/security-groups: sec-grp
11 | alb.ingress.kubernetes.io/conditions.flask-svc-alb-green: '[{"Field":"query-string","QueryStringConfig":{"Values":[{"Key":"group","Value":"green"}]}}]'
12 | alb.ingress.kubernetes.io/conditions.forward-multiple-tg: '[{"Field":"query-string","QueryStringConfig":{"Values":[{"Key":"group","Value":"blue"}]}}]'
13 | alb.ingress.kubernetes.io/actions.forward-multiple-tg: '{"Type":"forward","ForwardConfig":{"TargetGroups":[{"ServiceName":"flask-svc-alb-blue","ServicePort":"80","Weight":90},{"ServiceName":"flask-svc-alb-green","ServicePort":"8080","Weight":10}]}}'
14 |
15 | labels:
16 | app: flask-ingress
17 | spec:
18 | rules:
19 | - http:
20 | paths:
21 | - backend:
22 | serviceName: flask-svc-alb-green
23 | servicePort: 8080
24 | - backend:
25 | serviceName: forward-multiple-tg
26 | servicePort: use-annotation
27 | - path: /*
28 | backend:
29 | serviceName: flask-svc-alb-blue
30 | servicePort: 80
31 |
--------------------------------------------------------------------------------
/flask-docker-app/k8s/flaskBlue.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: flask-svc-blue
5 | spec:
6 | selector:
7 | app: flask-deploy-blue
8 | ports:
9 | - name: web
10 | port: 80
11 | targetPort: 5000
12 | type: LoadBalancer
13 | ---
14 | apiVersion: apps/v1beta1
15 | kind: Deployment
16 | metadata:
17 | labels:
18 | run: flask
19 | name: flask-deploy-blue
20 | spec:
21 | replicas: 1
22 | template:
23 | metadata:
24 | labels:
25 | app: flask-deploy-blue
26 | spec:
27 | containers:
28 | - name: flask
29 | image: nikunjv/flask-image:blue
30 | ports:
31 | - containerPort: 5000
32 | # command:
33 | # - "sh"
34 | # - "-c"
35 | # - "yum install -y python3-pip && tail -f /var/log/yum.log "
36 | resources:
37 | limits:
38 | memory: "500Mi"
39 | cpu: "0.25"
40 |
--------------------------------------------------------------------------------
/flask-docker-app/k8s/flaskGreen.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: flask-svc-green
5 | spec:
6 | selector:
7 | app: flask-deploy-green
8 | ports:
9 | - name: web
10 | port: 8080
11 | targetPort: 5000
12 | type: LoadBalancer
13 | ---
14 | apiVersion: apps/v1beta1
15 | kind: Deployment
16 | metadata:
17 | labels:
18 | run: flask
19 | name: flask-deploy-green
20 | spec:
21 | replicas: 1
22 | template:
23 | metadata:
24 | labels:
25 | app: flask-deploy-green
26 | spec:
27 | containers:
28 | - name: flask
29 | image: nikunjv/flask-image:blue
30 | ports:
31 | - containerPort: 5000
32 | # command:
33 | # - "sh"
34 | # - "-c"
35 | # - "yum install -y python3-pip && tail -f /var/log/yum.log "
36 | resources:
37 | limits:
38 | memory: "500Mi"
39 | cpu: "0.25"
40 |
--------------------------------------------------------------------------------
/flask-docker-app/k8s/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -x
4 |
5 | #Setup Env Vars
6 | export REGION=$1
7 | export NODE_ROLE_NAME=$2
8 | export CLUSTER_NAME=$3
9 |
10 | export ALB_POLICY_NAME=alb-ingress-controller
11 | policyExists=$(aws iam list-policies | jq '.Policies[].PolicyName' | grep alb-ingress-controller | tr -d '["\r\n]')
12 | if [[ "$policyExists" != "alb-ingress-controller" ]]; then
13 | echo "Policy does not exist, creating..."
14 | export ALB_POLICY_ARN=$(aws iam create-policy --region=$REGION --policy-name $ALB_POLICY_NAME --policy-document "https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/master/docs/examples/iam-policy.json" --query "Policy.Arn" | sed 's/"//g')
15 | aws iam attach-role-policy --region=$REGION --role-name=$NODE_ROLE_NAME --policy-arn=$ALB_POLICY_ARN
16 | fi
17 |
18 | #Create Ingress Controller
19 | if [ ! -f alb-ingress-controller.yaml ]; then
20 | wget https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.5/docs/examples/alb-ingress-controller.yaml
21 | fi
22 | sed -i "s/devCluster/$CLUSTER_NAME/g" alb-ingress-controller.yaml
23 | sed -i "s/# - --cluster-name/- --cluster-name/g" alb-ingress-controller.yaml
24 | kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.5/docs/examples/rbac-role.yaml
25 | kubectl apply -f alb-ingress-controller.yaml
26 |
27 | #Check
28 | kubectl get pods -n kube-system
29 | #kubectl logs -n kube-system $(kubectl get po -n kube-system | egrep -o "alb-ingress[a-zA-Z0-9-]+")
30 |
31 | #Attach IAM policy to Worker Node Role
32 | if [ ! -f iam-policy.json ]; then
33 | curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/master/docs/examples/iam-policy.json
34 | fi
35 | aws iam put-role-policy --role-name $NODE_ROLE_NAME --policy-name elb-policy --policy-document file://iam-policy.json
36 |
37 | #Instantiate Blue and Green PODS
38 | kubectl apply -f flask-ALB-namespace.yaml
39 | kubectl apply -f flaskALBBlue.yaml
40 | kubectl apply -f flaskALBGreen.yaml
41 |
42 | #Check
43 | kubectl get deploy -n flask-alb
44 | kubectl get svc -n flask-alb
45 | kubectl get pods -n flask-alb
46 |
47 | #Update Ingress Resource file and spawn ALB
48 | sg=$(aws ec2 describe-security-groups --filters Name=tag:aws:cloudformation:stack-name,Values=CdkStackALBEksBg | jq '.SecurityGroups[0].GroupId' | tr -d '["]')
49 | vpcid=$(aws ec2 describe-security-groups --filters Name=tag:aws:cloudformation:stack-name,Values=CdkStackALBEksBg | jq '.SecurityGroups[0].VpcId' | tr -d '["]')
50 | subnets=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$vpcid" "Name=tag:aws-cdk:subnet-name,Values=Public" | jq '.Subnets[0].SubnetId' | tr -d '["]')
51 | subnets="$subnets, $(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$vpcid" "Name=tag:aws-cdk:subnet-name,Values=Public" | jq '.Subnets[1].SubnetId' | tr -d '["]')"
52 | subnets="$subnets, $(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$vpcid" "Name=tag:aws-cdk:subnet-name,Values=Public" | jq '.Subnets[2].SubnetId' | tr -d '["]')"
53 |
54 | sed -i "s/public-subnets/$subnets/g" flaskALBIngress_query.yaml
55 | sed -i "s/public-subnets/$subnets/g" flaskALBIngress_query2.yaml
56 | sed -i "s/sec-grp/$sg/g" flaskALBIngress_query.yaml
57 | sed -i "s/sec-grp/$sg/g" flaskALBIngress_query2.yaml
58 | kubectl apply -f flaskALBIngress_query.yaml
59 |
--------------------------------------------------------------------------------
/flask-docker-app/k8s/setup2.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -x
4 |
5 | #Setup Env Vars
6 | export REGION=$1
7 | export NODE_ROLE_NAME=$2
8 | export CLUSTER_NAME=$3
9 |
10 | set +x
11 | echo "========================"
12 | echo "------CLEANUP BEGIN-----"
13 | echo "========================"
14 | set -x
15 | rm flaskALBIngress_query.yaml
16 | rm flaskALBIngress_query2.yaml
17 | wget https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/master/flask-docker-app/k8s/flaskALBIngress_query.yaml
18 | wget https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/master/flask-docker-app/k8s/flaskALBIngress_query2.yaml
19 | rm alb-ingress-controller.yaml
20 | kubectl delete svc/flask-svc-alb-blue svc/flask-svc-alb-green -n flask-alb
21 | kubectl delete deploy/flask-deploy-alb-blue deploy/flask-deploy-alb-green -n flask-alb
22 | kubectl delete ingress alb-ingress -n flask-alb
23 | kubectl delete deploy alb-ingress-controller -n kube-system
24 | set +x
25 | echo "======================"
26 | echo "------CLEANUP END-----"
27 | echo "======================"
28 | set -x
29 | kubectl get deploy -n flask-alb
30 | kubectl get svc -n flask-alb
31 | kubectl get pods -n flask-alb
32 | kubectl get ingress -n flask-alb
33 | kubectl get pods -n kube-system
34 | ls -al
35 | set +x
36 | echo "============================================"
37 | echo "------CAPTURE COMPLETE, BEGIN EXECUTION-----"
38 | echo "============================================"
39 |
40 | echo "Sleep for 5 seconds to allow termination of resources"
41 | set -x
42 | sleep 5
43 |
44 | export ALB_POLICY_NAME=alb-ingress-controller
45 | policyExists=$(aws iam list-policies | jq '.Policies[].PolicyName' | grep alb-ingress-controller | tr -d '["\r\n]')
46 | if [[ "$policyExists" != "alb-ingress-controller" ]]; then
47 | echo "Policy does not exist, creating..."
48 | export ALB_POLICY_ARN=$(aws iam create-policy --region=$REGION --policy-name $ALB_POLICY_NAME --policy-document "https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/master/docs/examples/iam-policy.json" --query "Policy.Arn" | sed 's/"//g')
49 | aws iam attach-role-policy --region=$REGION --role-name=$NODE_ROLE_NAME --policy-arn=$ALB_POLICY_ARN
50 | fi
51 |
52 | #Create Ingress Controller
53 | if [ ! -f alb-ingress-controller.yaml ]; then
54 | wget https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.5/docs/examples/alb-ingress-controller.yaml
55 | fi
56 | sed -i "s/devCluster/$CLUSTER_NAME/g" alb-ingress-controller.yaml
57 | sed -i "s/# - --cluster-name/- --cluster-name/g" alb-ingress-controller.yaml
58 | kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.5/docs/examples/rbac-role.yaml
59 | kubectl apply -f alb-ingress-controller.yaml
60 |
61 | #Check
62 | kubectl get pods -n kube-system
63 | #kubectl logs -n kube-system $(kubectl get po -n kube-system | egrep -o "alb-ingress[a-zA-Z0-9-]+")
64 |
65 | #Attach IAM policy to Worker Node Role
66 | if [ ! -f iam-policy.json ]; then
67 | curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/master/docs/examples/iam-policy.json
68 | fi
69 | aws iam put-role-policy --role-name $NODE_ROLE_NAME --policy-name elb-policy --policy-document file://iam-policy.json
70 |
71 | #Instantiate Blue and Green PODS
72 | kubectl apply -f flask-ALB-namespace.yaml
73 | kubectl apply -f flaskALBBlue.yaml
74 | kubectl apply -f flaskALBGreen.yaml
75 |
76 | #Check
77 | kubectl get deploy -n flask-alb
78 | kubectl get svc -n flask-alb
79 | kubectl get pods -n flask-alb
80 |
81 | #Update Ingress Resource file and spawn ALB
82 | sg=$(aws ec2 describe-security-groups --filters Name=tag:aws:cloudformation:stack-name,Values=CdkStackALBEksBg | jq '.SecurityGroups[0].GroupId' | tr -d '["]')
83 | vpcid=$(aws ec2 describe-security-groups --filters Name=tag:aws:cloudformation:stack-name,Values=CdkStackALBEksBg | jq '.SecurityGroups[0].VpcId' | tr -d '["]')
84 | subnets=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$vpcid" "Name=tag:aws-cdk:subnet-name,Values=Public" | jq '.Subnets[0].SubnetId' | tr -d '["]')
85 | subnets="$subnets, $(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$vpcid" "Name=tag:aws-cdk:subnet-name,Values=Public" | jq '.Subnets[1].SubnetId' | tr -d '["]')"
86 | subnets="$subnets, $(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$vpcid" "Name=tag:aws-cdk:subnet-name,Values=Public" | jq '.Subnets[2].SubnetId' | tr -d '["]')"
87 |
88 | sed -i "s/public-subnets/$subnets/g" flaskALBIngress_query.yaml
89 | sed -i "s/public-subnets/$subnets/g" flaskALBIngress_query2.yaml
90 | sed -i "s/sec-grp/$sg/g" flaskALBIngress_query.yaml
91 | sed -i "s/sec-grp/$sg/g" flaskALBIngress_query2.yaml
92 | kubectl apply -f flaskALBIngress_query.yaml
93 | set +x
94 | echo "================"
95 | echo "--CHECK OUTPUT--"
96 | echo "================"
97 | set -x
98 | kubectl get deploy -n flask-alb
99 | kubectl get svc -n flask-alb
100 | kubectl get ingress -n flask-alb
101 | kubectl get pods -n kube-system
102 | kubectl get pods -n flask-alb
103 | set +x
104 | echo "========================"
105 | echo "------END EXECUTION-----"
106 | echo "========================"
107 |
108 | #Add cluster sg ingress rule from alb source
109 | CLUSTER_SG=$(aws eks describe-cluster --name $CLUSTER_NAME --query cluster.resourcesVpcConfig.clusterSecurityGroupId | tr -d '["]')
110 |
111 | aws ec2 authorize-security-group-ingress \
112 | --group-id $CLUSTER_SG \
113 | --protocol -1 \
114 | --port -1 \
115 | --source-group $sg
--------------------------------------------------------------------------------
/flask-docker-app/pyvenv.cfg:
--------------------------------------------------------------------------------
1 | home = /Users/pahud/homebrew/bin
2 | include-system-site-packages = false
3 | version = 3.7.4
4 |
--------------------------------------------------------------------------------
/flask-docker-app/requirements.txt:
--------------------------------------------------------------------------------
1 | Click==7.0
2 | Flask==1.1.1
3 | itsdangerous==1.1.0
4 | Jinja2==2.11.3
5 | MarkupSafe==1.1.1
6 | Werkzeug==0.15.6
7 |
--------------------------------------------------------------------------------
/flask-docker-app/static/css/bootstrap-responsive.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Responsive v2.2.2
3 | *
4 | * Copyright 2012 Twitter, Inc
5 | * Licensed under the Apache License v2.0
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Designed and built with all the love in the world @twitter by @mdo and @fat.
9 | */@-ms-viewport{width:device-width}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:hover{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}}
10 |
--------------------------------------------------------------------------------
/flask-docker-app/templates/hello.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Your Flask application is now running on a container in {{ platform }}
21 |The container is running Flask version {{ flask_version }} and Python {{ python_version }}
22 | 23 | -------------------------------------------------------------------------------- /images/alb-dns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/images/alb-dns.png -------------------------------------------------------------------------------- /images/alb-tg-check1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/images/alb-tg-check1.png -------------------------------------------------------------------------------- /images/alb-tg-check2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/images/alb-tg-check2.png -------------------------------------------------------------------------------- /images/canary-lb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/images/canary-lb.png -------------------------------------------------------------------------------- /images/cfn-kubectl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/images/cfn-kubectl.png -------------------------------------------------------------------------------- /images/eks-bg-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/images/eks-bg-1.png -------------------------------------------------------------------------------- /images/eks-bg-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/images/eks-bg-2.png -------------------------------------------------------------------------------- /images/eks-canary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/images/eks-canary.png -------------------------------------------------------------------------------- /images/eks-cicd-codebuild.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/images/eks-cicd-codebuild.png -------------------------------------------------------------------------------- /images/flask01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/images/flask01.png -------------------------------------------------------------------------------- /images/flask02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/images/flask02.png -------------------------------------------------------------------------------- /images/stage12-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/images/stage12-green.png -------------------------------------------------------------------------------- /images/stage34-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/images/stage34-green.png -------------------------------------------------------------------------------- /images/web-blue-inv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/images/web-blue-inv.png -------------------------------------------------------------------------------- /images/web-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/images/web-blue.png -------------------------------------------------------------------------------- /images/web-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/images/web-default.png -------------------------------------------------------------------------------- /images/web-green-inv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/images/web-green-inv.png -------------------------------------------------------------------------------- /images/web-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-cdk-blue-green-cicd/872efe33a3511f9c1d28676663e30303c8bbd59a/images/web-green.png --------------------------------------------------------------------------------