Bootstrapping an Amazon EC2 Instance Using User-data to Run a Python Web App
Deploy a Python web application to an EC2 instance running Nginx and uWSGI, using a CI/CD Pipeline created with Amazon CDK.
systemd
service for uWSGI, and copy our application using CDK. Then, we are going to deploy our Python-based web application from a GitHub repository. We will cover how to:- Create an AWS CDK stack with an Amazon EC2 instance, a CI/CD Pipeline, and the required resources for it to operate.
- Install software packages on the EC2 instance's first launch by creating a user data asset.
- Test, Deploy and Configure the web application using the CI/CD pipeline.
About | |
---|---|
✅ AWS experience | 200 - Intermediate |
⏱ Time to complete | 60 minutes |
💰 Cost to complete | Free tier eligible |
🧩 Prerequisites | - AWS account -CDK installed: Visit Get Started with AWS CDK to learn more. |
💻 Code Sample | Code sample used in tutorial on GitHub |
📢 Feedback | Any feedback, issues, or just a 👍 / 👎 ? |
⏰ Last Updated | 2024-01-29 |
1
2
3
cdk --version
# 2.122.0 (build 7e77e02)
1.x.x
, or you just want to ensure you are on the latest version, run the following:1
npm install -g aws-cdk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
mkdir ec2-cdk
cd ec2-cdk
cdk init app --language typescript
# Output:
Applying project template app for typescript
# Welcome to your CDK TypeScript project
This is a blank project for CDK development with TypeScript.
The `cdk.json` file tells the CDK Toolkit how to execute your app.
## Useful commands
* `npm run build` compile typescript to js
* `npm run watch` watch for changes and compile
* `npm run test` perform the jest unit tests
* `cdk deploy` deploy this stack to your default AWS account/region
* `cdk diff` compare deployed stack with current state
* `cdk synth` emits the synthesized CloudFormation template
Initializing a new git repository...
Executing npm install...
npm WARN deprecated w3c-hr-time@1.0.2: Use your platform's native performance.now() and performance.timeOrigin.
npm notice
npm notice New patch version of npm available! 8.19.2 → 8.19.3
npm notice Changelog: https://github.com/npm/cli/releases/tag/v8.19.3
npm notice Run npm install -g npm@8.19.3 to update!
npm notice
✅ All done!
ec2-cdk
. If you named your directory differently, please replace this with the folder name you used. To start adding infrastructure, go to the file lib/ec2-cdk-stack.ts
. This is where we will write the code for the resource stack you are going to create.- IAM roles: This role will be assigned to the EC2 instance to allow it to call other AWS services.
- EC2 instance: The virtual machine you will use to host your web application.
- Security group: The virtual firewall to allow inbound requests to your web application.
- Secrets manager secret: This is a place where you will store your Github Token that we will use to authenticate the pipeline to it.
lib/ec2-cdk-stack.ts
):1
2
3
4
5
6
import { readFileSync } from 'fs';
import { Vpc, SubnetType, Peer, Port, AmazonLinuxGeneration,
AmazonLinuxCpuType, Instance, SecurityGroup, AmazonLinuxImage,
InstanceClass, InstanceSize, InstanceType
} from 'aws-cdk-lib/aws-ec2';
import { Role, ServicePrincipal, ManagedPolicy } from 'aws-cdk-lib/aws-iam';
1
2
3
4
5
6
7
8
9
10
11
12
const webServerRole = new Role(this, "ec2Role", {
assumedBy: new ServicePrincipal("ec2.amazonaws.com"),
});
// IAM policy attachment to allow access to
webServerRole.addManagedPolicy(
ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMManagedInstanceCore")
);
webServerRole.addManagedPolicy(
ManagedPolicy.fromAwsManagedPolicyName("service-role/AmazonEC2RoleforAWSCodeDeploy")
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// This VPC has 3 public subnets, and that's it
const vpc = new Vpc(this, 'main_vpc',{
subnetConfiguration: [
{
cidrMask: 24,
name: 'pub01',
subnetType: SubnetType.PUBLIC,
},
{
cidrMask: 24,
name: 'pub02',
subnetType: SubnetType.PUBLIC,
},
{
cidrMask: 24,
name: 'pub03',
subnetType: SubnetType.PUBLIC,
}
]
});
http
(port 80). To allow traffic to this port, we need to set up firewall rules by creating a security group. We will set up port 80 to allow HTTP traffic to come to the instance from any location on the internet.1
2
3
4
5
6
7
8
9
10
11
12
// Security Groups
// This SG will only allow HTTP traffic to the Web server
const webSg = new SecurityGroup(this, 'web_sg',{
vpc,
description: "Allows Inbound HTTP traffic to the web server.",
allowAllOutbound: true,
});
webSg.addIngressRule(
Peer.anyIpv4(),
Port.tcp(80)
);
t2.micro
that has 1 vCPU and 1GB of memory. If you are running this tutorial in one of the newer AWS Regions, the t2.micro
type may not be available. Just use the t3.micro
one instead. To view all the different instance types, see the EC2 instance types page.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// the AMI to be used for the EC2 Instance
const ami = new AmazonLinuxImage({
generation: AmazonLinuxGeneration.AMAZON_LINUX_2023,
cpuType: AmazonLinuxCpuType.X86_64,
});
// The actual Web EC2 Instance for the web server
const webServer = new Instance(this, 'web_server',{
vpc,
instanceType: InstanceType.of(
InstanceClass.T2,
InstanceSize.MICRO,
),
machineImage: ami,
securityGroup: webSg,
role: webServerRole,
});
configure_amz_linux_sample_app.sh
in the assets
directory in the root of your CDK Application.1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Install OS packages
yum update -y
yum groupinstall -y "Development Tools"
amazon-linux-extras install -y nginx1
yum install -y nginx python3.11 python3.11-pip python3.11-devel ruby wget
python3.11 -m pip install pipenv wheel
python3.11 -m pip install uwsgi
# Code Deploy Agent
cd /home/ec2-user
wget https://aws-codedeploy-us-west-2.s3.us-west-2.amazonaws.com/latest/install
chmod +x ./install
./install auto
1
2
3
4
5
6
// User data - used for bootstrapping
const webSGUserData = readFileSync('./assets/configure_amz_linux_sample_app.sh','utf-8');
webServer.addUserData(webSGUserData);
// Tag the instance
cdk.Tags.of(webServer).add('application-name','python-web')
cdk.Tags.of(webServer).add('stage','prod')
1
2
3
4
// Output the public IP address of the EC2 instance
new cdk.CfnOutput(this, "IP Address", {
value: webServer.instancePublicIp,
});
lib/ec2-cdk-stack.ts
file should now look like this:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import * as cdk from 'aws-cdk-lib';
import { readFileSync } from 'fs';
import { Construct } from 'constructs';
import { Vpc, SubnetType, Peer, Port, AmazonLinuxGeneration,
AmazonLinuxCpuType, Instance, SecurityGroup, AmazonLinuxImage,
InstanceClass, InstanceSize, InstanceType
} from 'aws-cdk-lib/aws-ec2';
import { Role, ServicePrincipal, ManagedPolicy } from 'aws-cdk-lib/aws-iam';
export class Ec2CdkStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// IAM
// Policy for CodeDeploy bucket access
// Role that will be attached to the EC2 instance so it can be
// managed by AWS SSM
const webServerRole = new Role(this, "ec2Role", {
assumedBy: new ServicePrincipal("ec2.amazonaws.com"),
});
// IAM policy attachment to allow access to
webServerRole.addManagedPolicy(
ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMManagedInstanceCore")
);
webServerRole.addManagedPolicy(
ManagedPolicy.fromAwsManagedPolicyName("service-role/AmazonEC2RoleforAWSCodeDeploy")
);
// VPC
// This VPC has 3 public subnets, and that's it
const vpc = new Vpc(this, 'main_vpc',{
subnetConfiguration: [
{
cidrMask: 24,
name: 'pub01',
subnetType: SubnetType.PUBLIC,
},
{
cidrMask: 24,
name: 'pub02',
subnetType: SubnetType.PUBLIC,
},
{
cidrMask: 24,
name: 'pub03',
subnetType: SubnetType.PUBLIC,
}
]
});
// Security Groups
// This SG will only allow HTTP traffic to the Web server
const webSg = new SecurityGroup(this, 'web_sg',{
vpc,
description: "Allows Inbound HTTP traffic to the web server.",
allowAllOutbound: true,
});
webSg.addIngressRule(
Peer.anyIpv4(),
Port.tcp(80)
);
// EC2 Instance
// This is the Python Web server that we will be using
// Get the latest AmazonLinux 2 AMI for the given region
const ami = new AmazonLinuxImage({
generation: AmazonLinuxGeneration.AMAZON_LINUX_2023,
cpuType: AmazonLinuxCpuType.X86_64,
});
// The actual Web EC2 Instance for the web server
const webServer = new Instance(this, 'web_server',{
vpc,
instanceType: InstanceType.of(
InstanceClass.T2,
InstanceSize.MICRO,
),
machineImage: ami,
securityGroup: webSg,
role: webServerRole,
});
// User data - used for bootstrapping
const webSGUserData = readFileSync('./assets/configure_amz_linux_sample_app.sh','utf-8');
webServer.addUserData(webSGUserData);
// Tag the instance
cdk.Tags.of(webServer).add('application-name','python-web')
cdk.Tags.of(webServer).add('stage','prod')
// Output the public IP address of the EC2 instance
new cdk.CfnOutput(this, "IP Address", {
value: webServer.instancePublicIp,
});
}
}
- To provide authentication to stage, commit, and push code from local repo to the GitHub repo. You may also use SSH keys for this.
- To connect GitHub to CodePipeline, so whenever new code is committed to GitHub repo it automatically triggers pipeline execution.
github-oauth-token
.GITHUB_ACCESS_TOKEN
with your plaintext secret and REGION
in following command and run it:1
2
3
4
5
aws secretsmanager create-secret \
--name github-oauth-token \
--description "Github access token for cdk" \
--secret-string GITHUB_ACCESS_TOKEN \
--region REGION
unittest
Python Unit Testing Framework; and 3/ Deploy - Deploying and configuring the web application on the EC2 instance using AWS CodeDeploy. Let's get back to CDK.lib/ec2-cdk-stack.ts
:1
2
3
4
5
import { Pipeline, Artifact } from 'aws-cdk-lib/aws-codepipeline';
import { GitHubSourceAction, CodeBuildAction, CodeDeployServerDeployAction } from 'aws-cdk-lib/aws-codepipeline-actions';
import { PipelineProject, LinuxBuildImage } from 'aws-cdk-lib/aws-codebuild';
import { ServerDeploymentGroup, ServerApplication, InstanceTagSet } from 'aws-cdk-lib/aws-codedeploy';
import { SecretValue } from 'aws-cdk-lib';
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// CodePipeline
const pipeline = new Pipeline(this, 'python_web_pipeline',{
pipelineName: 'python-webApp',
crossAccountKeys: false, // solves the encrypted bucket issue
});
// STAGES
// Source Stage
const sourceStage = pipeline.addStage({
stageName: 'Source',
})
// Build Stage
const buildStage = pipeline.addStage({
stageName: 'Build',
})
// Deploy Stage
const deployStage = pipeline.addStage({
stageName: 'Deploy',
})
Source
stage, as here is where we connect the pipeline to Github, so it can retrieve our code commits to be passed down the pipeline. Some important parts to take note of here: make sure to set up the github token as a secret in AWS Secrets Manager (check the steps above), and ensure to change the owner
parameter to match that of your GitHub username:1
2
3
4
5
6
7
8
9
10
11
12
// Source action
const sourceOutput = new Artifact();
const githubSourceAction = new GitHubSourceAction({
actionName: 'GithubSource',
oauthToken: SecretValue.secretsManager('github-oauth-token'), // MAKE SURE TO SET UP BEFORE
owner: 'darko-mesaros', // THIS NEEDS TO BE CHANGED TO YOUR OWN USER ID
repo: 'sample-python-web-app',
branch: 'main',
output: sourceOutput,
});
sourceStage.addAction(githubSourceAction);
Build
stage: we are not actually building anything, but rather testing the code. In this stage, we are running unit tests against our code (which we will set up later), and if successful, it continues along to the next next stage.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Build Action
const pythonTestProject = new PipelineProject(this, 'pythonTestProject',{
environment: {
buildImage: LinuxBuildImage.AMAZON_LINUX_2_5
}
});
const pythonTestOutput = new Artifact();
const pythonTestAction = new CodeBuildAction({
actionName: 'TestPython',
project: pythonTestProject,
input: sourceOutput,
outputs: [pythonTestOutput]
});
buildStage.addAction(pythonTestAction);
Deploy
stage: this stage uses CodeDeploy to deploy and configure the web application on the EC2 instance. For this to work, we need to have the CodeDeploy agent installed and running on the instance (which we did before with the user data), and also we need to tell CodeDeploy which instances to target for deployment. We will be using tags for this. If you recall, earlier in this tutorial we tagged the EC2 instance with specific tags. Now we are using those tags to target the instances with CodeDeploy, and deploy the code.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Deploy Actions
const pythonDeployApplication = new ServerApplication(this,"python_deploy_application",{
applicationName: 'python-webApp'
});
// Deployment group
const pythonServerDeploymentGroup = new ServerDeploymentGroup(this,'PythonAppDeployGroup',{
application: pythonDeployApplication,
deploymentGroupName: 'PythonAppDeploymentGroup',
installAgent: true,
ec2InstanceTags: new InstanceTagSet(
{
'application-name': ['python-web'],
'stage':['prod', 'stage']
})
});
// Deployment action
const pythonDeployAction = new CodeDeployServerDeployAction({
actionName: 'PythonAppDeployment',
input: sourceOutput,
deploymentGroup: pythonServerDeploymentGroup,
});
deployStage.addAction(pythonDeployAction);
lib/ec2-cdk-stack.ts
file should look like this:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
import * as cdk from 'aws-cdk-lib';
import { readFileSync } from 'fs';
import { Construct } from 'constructs';
import { Vpc, SubnetType, Peer, Port, AmazonLinuxGeneration,
AmazonLinuxCpuType, Instance, SecurityGroup, AmazonLinuxImage,
InstanceClass, InstanceSize, InstanceType
} from 'aws-cdk-lib/aws-ec2';
import { Role, ServicePrincipal, ManagedPolicy } from 'aws-cdk-lib/aws-iam';
import { Pipeline, Artifact } from 'aws-cdk-lib/aws-codepipeline';
import { GitHubSourceAction, CodeBuildAction, CodeDeployServerDeployAction } from 'aws-cdk-lib/aws-codepipeline-actions';
import { PipelineProject, LinuxBuildImage } from 'aws-cdk-lib/aws-codebuild';
import { ServerDeploymentGroup, ServerApplication, InstanceTagSet } from 'aws-cdk-lib/aws-codedeploy';
import { SecretValue } from 'aws-cdk-lib';
export class Ec2CdkStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// IAM
// Policy for CodeDeploy bucket access
// Role that will be attached to the EC2 instance so it can be
// managed by AWS SSM
const webServerRole = new Role(this, "ec2Role", {
assumedBy: new ServicePrincipal("ec2.amazonaws.com"),
});
// IAM policy attachment to allow access to
webServerRole.addManagedPolicy(
ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMManagedInstanceCore")
);
webServerRole.addManagedPolicy(
ManagedPolicy.fromAwsManagedPolicyName("service-role/AmazonEC2RoleforAWSCodeDeploy")
);
// VPC
// This VPC has 3 public subnets, and that's it
const vpc = new Vpc(this, 'main_vpc',{
subnetConfiguration: [
{
cidrMask: 24,
name: 'pub01',
subnetType: SubnetType.PUBLIC,
},
{
cidrMask: 24,
name: 'pub02',
subnetType: SubnetType.PUBLIC,
},
{
cidrMask: 24,
name: 'pub03',
subnetType: SubnetType.PUBLIC,
}
]
});
// Security Groups
// This SG will only allow HTTP traffic to the Web server
const webSg = new SecurityGroup(this, 'web_sg',{
vpc,
description: "Allows Inbound HTTP traffic to the web server.",
allowAllOutbound: true,
});
webSg.addIngressRule(
Peer.anyIpv4(),
Port.tcp(80)
);
// EC2 Instance
// This is the Python Web server that we will be using
// Get the latest AmazonLinux 2 AMI for the given region
const ami = new AmazonLinuxImage({
generation: AmazonLinuxGeneration.AMAZON_LINUX_2023,
cpuType: AmazonLinuxCpuType.X86_64,
});
// The actual Web EC2 Instance for the web server
const webServer = new Instance(this, 'web_server',{
vpc,
instanceType: InstanceType.of(
InstanceClass.T3,
InstanceSize.MICRO,
),
machineImage: ami,
securityGroup: webSg,
role: webServerRole,
});
// User data - used for bootstrapping
const webSGUserData = readFileSync('./assets/configure_amz_linux_sample_app.sh','utf-8');
webServer.addUserData(webSGUserData);
// Tag the instance
cdk.Tags.of(webServer).add('application-name','python-web')
cdk.Tags.of(webServer).add('stage','prod')
// Pipeline stuff
// CodePipeline
const pipeline = new Pipeline(this, 'python_web_pipeline', {
pipelineName: 'python-webApp',
crossAccountKeys: false, // solves the encrypted bucket issue
});
// STAGES
// Source Stage
const sourceStage = pipeline.addStage({
stageName: 'Source',
});
// Build Stage
const buildStage = pipeline.addStage({
stageName: 'Build',
});
// Deploy Stage
const deployStage = pipeline.addStage({
stageName: 'Deploy',
});
// Add some action
// Source action
const sourceOutput = new Artifact();
const githubSourceAction = new GitHubSourceAction({
actionName: 'GithubSource',
oauthToken: SecretValue.secretsManager('github-oauth-token'), // SET UP BEFORE
owner: 'darko-mesaros', // THIS NEEDS TO BE CHANGED TO THE READER
repo: 'sample-python-web-app',
branch: 'main',
output: sourceOutput,
});
sourceStage.addAction(githubSourceAction);
// Build Action
const pythonTestProject = new PipelineProject(this, 'pythonTestProject', {
environment: {
buildImage: LinuxBuildImage.AMAZON_LINUX_2_5
}
});
const pythonTestOutput = new Artifact();
const pythonTestAction = new CodeBuildAction({
actionName: 'TestPython',
project: pythonTestProject,
input: sourceOutput,
outputs: [pythonTestOutput]
});
buildStage.addAction(pythonTestAction);
// Deploy Actions
const pythonDeployApplication = new ServerApplication(this,"python_deploy_application", {
applicationName: 'python-webApp'
});
// Deployment group
const pythonServerDeploymentGroup = new ServerDeploymentGroup(this,'PythonAppDeployGroup', {
application: pythonDeployApplication,
deploymentGroupName: 'PythonAppDeploymentGroup',
installAgent: true,
ec2InstanceTags: new InstanceTagSet(
{
'application-name': ['python-web'],
'stage':['prod', 'stage']
})
});
// Deployment action
const pythonDeployAction = new CodeDeployServerDeployAction({
actionName: 'PythonAppDeployment',
input: sourceOutput,
deploymentGroup: pythonServerDeploymentGroup,
});
deployStage.addAction(pythonDeployAction);
// Output the public IP address of the EC2 instance
new cdk.CfnOutput(this, "IP Address", {
value: webServer.instancePublicIp,
});
}
}
tests
directory, and add the following test_sample.py
file to it:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import unittest
from application import application
class TestHello(unittest.TestCase):
def setUp(self):
application.testing = True
self.application = application.test_client()
def test_hello(self):
rv = self.application.get('/')
self.assertEqual(rv.status, '200 OK')
if __name__ == '__main__':
import xmlrunner
unittest.main(testRunner=xmlrunner.XMLTestRunner(output='test-reports'))
unittest.main()
200
HTTP status code. Simple as that. On top of this file, just for posterity, let's create a __init__.py
file in the same directory. This file can be empty so you can just create it with the following command:1
touch tests/__init__.py
buildspec.yml
file. This file is used by CodeDeploy as an instruction set of what it needs to do to build your code. In our case, we are instructing it on how to run the tests. In the root directory of the sample application, add the buildspec.yml
file with the following contents:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
version: 0.2
phases:
install:
runtime-versions:
python: 3.11
commands:
- echo Entered the install phase...
- pip install pipenv
- pipenv install
finally:
- echo This always runs even if the update or install command fails
build:
commands:
- echo Entered the build phase...
- echo Build started on `date`
- pipenv run python -m unittest # not an interactive session so we need to run
finally:
- echo This always runs even if the install command fails
post_build:
commands:
- echo Entered the post_build phase...
- echo Build completed on `date`
appspec.yml
as an instruction set on how to deploy your application to its final destination. On top of that file, we will be adding a few shell scripts to configure and launch the application on the server. This is needed as we need to create a specific nginx
website, and do some service restarts. But let's first create the appspec.yml
file in the root of the sample application directory, with the following contents:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
version: 0.0
os: linux
files:
- source: /
destination: /var/www/SampleApp
hooks:
BeforeInstall:
- location: scripts/setup_dirs.sh
timeout: 300
runas: root
AfterInstall:
- location: scripts/setup_services.sh
- location: scripts/pipenv.sh
timeout: 300
runas: root
ApplicationStart:
- location: scripts/start_server.sh
timeout: 300
runas: root
scripts
in the root of the sample application. These scripts should be named as follows, and should contain the following contents:setup_dirs.sh
1
2
3
4
mkdir -p /var/www/SampleApp
chown nginx:nginx /var/www
chown nginx:nginx /var/www/SampleApp
setup_services.sh
1
2
3
4
5
6
7
8
9
10
11
12
## Install uWSGI as a systemd service, enable it to run at boot, then start it
cp /var/www/SampleApp/sample-app.uwsgi.service /etc/systemd/system/mywebapp.uwsgi.service
mkdir -p /var/log/uwsgi
chown nginx:nginx /var/log/uwsgi
systemctl enable mywebapp.uwsgi.service
## Copy the nginx config file, then ensure nginx starts at boot, and restart it to load the config
cp /var/www/SampleApp/nginx-app.conf /etc/nginx/conf.d/nginx-app.conf
mkdir -p /var/log/nginx
chown nginx:nginx /var/log/nginx
systemctl enable nginx.service
pipenv.sh
1
2
3
4
5
chown nginx:nginx -R /var/www/SampleApp/
cd /var/www/SampleApp
pipenv install
start_server.sh
1
2
3
systemctl restart mywebapp.uwsgi.service
systemctl restart nginx.service
Pipfile
at the root of your sample application repository so it reflects the following:1
2
3
4
5
6
7
8
9
10
11
12
13
14
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[ ]
[ ]
flask = "*"
boto3 = "*"
uwsgi = "*"
[ ]
python_version = "3.11"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
├── application.config
├── application.py
├── appspec.yml
├── buildspec.yml
├── CODE_OF_CONDUCT.md
├── configure_amz_linux_sample_app.sh
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── nginx-app.conf
├── Pipfile
├── README.md
├── sample-app.uwsgi.service
├── scripts
│ ├── pipenv.sh
│ ├── setup_dirs.sh
│ ├── setup_services.sh
│ └── start_server.sh
├── start.sh
├── static
│ ├── bootstrap
│ └── jquery
├── templates
│ └── index.html
└── tests
├── __init__.py
├── __pycache__
└── test_sample.py
bin/ec2-cdk.ts
and uncomment line 14:1
env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
cdk bootstrap
(your account ID will be different from the placeholder ones below):1
2
3
4
5
6
cdk bootstrap
#output
⏳ Bootstrapping environment aws://0123456789012/<region>...
✅ Environment aws://0123456789012/<region> bootstrapped
Deploying the stack
1
cdk deploy
y
to continue with the deployment and create the resources. The CLI will show the deployment progress, and in the end, the output we defined in our CDK app.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Do you wish to deploy these changes (y/n)? y
PythonEc2BlogpostStack: deploying...
[0%] start: Publishing afe67465ec62603d27d77795221a45e68423c87495467b0265ecdadad80bb5e2:current
[33%] success: Published afe67465ec62603d27d77795221a45e68423c87495467b0265ecdadad80bb5e2:current
[33%] start: Publishing 73887b77b71ab7247eaf6dc4647f03f9f1cf8f0da685460f489ec8f2106d480d:current
[66%] success: Published 73887b77b71ab7247eaf6dc4647f03f9f1cf8f0da685460f489ec8f2106d480d:current
[66%] start: Publishing 13138ebf2da51426144f6f5f4f0ad197787f52aad8b6ceb26ecff68d33cd2b78:current
[100%] success: Published 13138ebf2da51426144f6f5f4f0ad197787f52aad8b6ceb26ecff68d33cd2b78:current
Ec2CdkStack: creating CloudFormation changeset...
✅ PythonEc2BlogpostStack
✨ Deployment time: 27.74s
Outputs:
PythonEc2BlogpostStack.IPAddress = x.x.x.x
Stack ARN:
arn:aws:cloudformation:us-west-2:123456789000:stack/Ec2CdkStack/59f1e560-grunf-11ed-afno1-06f3bbc9cf63
✨ Total time: 29.11s
python-webApp
pipeline. There you should see something similar to this:Deploy
stage should be green), copy and then paste the IP address of your EC2 instance in your browser, and your sample application should be up and running. Congratulations! You have set up a Python web application running on an EC2 instance, with a CI/CD pipeline to test and deploy and changes!cdk destroy
command. This will only remove infrastructure created during this tutorial in our CDK application. You will see a confirmation:1
2
3
4
5
6
cdk destroy
# Enter y to approve the changes and delete any stack resources.
PythonEc2BlogpostStack: destroying ...
✅ PythonEc2BlogpostStack: destroyed
PythonEc2BlogpostStack: destroyed
, your resources have been removed. There is one more step for the cleanup: removing the S3 bucket used by CDK to upload the scripts and sample application. These resources aren't deleted by CDK as a safety precaution. Open the S3 console in your browser, and look for a bucket with a name like pythonec2blockpoststack-<randonmunbers>-us-east-1
(yours will have a different random number and your account number instead of 123456789012
). If you see more than one (usually if you have used the CDK asset feature before), you can sort by Creation Date
to see the latest created one. Open the bucket to confirm that you see a directory called python-webApp
. Select all the directory, then choose actions
-> delete
, and follow the prompts to delete the objects. Lastly, go back to the S3 console, and delete the bucket.Any opinions in this post are those of the individual author and may not reflect the opinions of AWS.