Auto scaling Laravel App with CodeDeploy
Auto-scaling Laravel App with GitLab CI and AWS Code Deploy
ဒီနေ့ ပြောပြပေးမှာကတော့ Laravel app တစ်ခုကို AWS Application Load Balancer အောက်မှာ EC2 Autoscaling သုံးပြီး AWS CodeDeploy ကနေ Blue/Green deplotyment လုပ်တဲ့နည်းပဲ ဖြစ်ပါတယ်။
ဒီ topic က အရင်ရက်က ပြောခဲ့တာတွေထက်စာရင် နည်းနည်း advance ပိုဖြစ်တဲ့ အတွက်၊ ဒီ ဆောင်းပါးကို ဖတ်မယ်ဆိုရင် AWS services တွေနဲ့ ရင်းနှီးပြီးသား လူဖြစ်ဖို့လို့ပါတယ်။ မရင်းနှီးသေးရင်တော့ ဒီဂိမ်း (About AWS Cloud Quest) ကို အရင်ဆော့ပြီး ရင်နှီးသွားအောင်လုပ်ဖို့ အကြံပေးပါရစေ။
Laravel backend ကို Auto Scaling သုံးမယ်ဆိုရင် ဒီအချက်တွေ သတိထားရပါတယ်။
Cache , Session driver တွေကို redis သုံးသင့်ပါတယ်၊ redis ကိုလည်း သီးသန့် server မှာ ထားရပါမယ်၊ ဖြစ်နိုင်ရင် AWS ElasticCache သုံးသင့်ပါတယ်။
Database ကိုလည်း သီးသန့် server မှာထားသင့်ပါတယ်၊ ဖြစ်နိုင်ရင် RDS သုံးသင့်ပါတယ်။
Storage Driver ကိုလည်း S3 သုံးသင့်ပါတယ်။
Schedule တွေ duplicate မဖြစ်ဖို့
onOneServer()
function သင့်ပါတယ်။
ဒီအချက်တွေအတိုင်း လုပ်ထားမှ Server သုံးလေးလုံးမျှပြီး အလုပ်လုပ်တဲ့ အခါ အဆင်ပြေပြေ လုပ်နိုင်မှာ ဖြစ်ပါတယ်။ ဒီနေ့က Deployment ပိုင်းကို အဓိက ပြောမှာ ဖြစ်တဲ့အတွက် အပေါ်က Services တွေ Laravel ထဲမှာ configure လုပ်တဲ့အပိုင်းကို တော့ ထည့်မပြောတော့ပါဘူး။
Required AWS Services
ဒီ Deployment အတွက် အသုံးပြုသွားမဲ့ AWS Services တွေကတော့
S3 bucket
- Deployment file တွေနဲ့ config တွေ သိမ်းဖို့
IAM user (GitlabDeployer)
- deployment package ကို S3 ပေါ် တင်ဖို့နဲ့ CodeDeploy app ကို start လုပ်ဖို့
IAM service role for EC2 Instance
- Deployment S3 bucket ပေါ်က ဖိုင်တွေကို Read access ရဖို့
IAM service role for CodeDeploy
- CodeDeploy application ကို စီမံခွင့်ရဖို့
EC2 ALB
EC2 Target Group
EC2 Launch Template
EC2 Auto Scaling Group
CodeDeploy Application and Group
စတဲ့ services တွေကို လိုအပ်ပါတယ်။
Setup S3
S3 မှာ bucket တစ်ခု ဆောက်ပါ၊ region ကို singapore ရွေးပြီး ကျန်တာတွေအကုန်လုံး ဒီအတိုင်းပဲထားပါ။ ကျွန်တော်တို့ ဒီဆောင်းပါးမှာတော့ bucket name ကို
kalaung-codedeploy
လို့ သုံးပါမယ်။php-fpm.conf
,production.nginx.conf
ဖိုင်တွေကို s3 ပေါ် upload တင်ပါ။ သုံးရမဲ့ ဖိုင်တွေကို ဒီဆောင်းပါးရဲ့ အောက်ဆုံးမှာ စုပေးထားပါမယ်။ ကိုယ့် project ရဲ့ လိုအပ်ချက်ပေါ်မူတည်ပြီး လိုအပ်သလို ပြင်ပြီးမှ တင်ပါ။Project မှာ သုံးမဲ့
.env
ဖိုင်ကိုproduction.env
လို့ နာမည်ပေးပြီး s3 ပေါ် upload တင်ပါ။
Setup IAM (policies, roles, user)
IAM >> Policies ထဲမှာ Create policy ကို နှိပ်ပြီး ဒီ Policy လေးခုဆောက်ပါ။ Policy ထဲထည့်ရမဲ့ code တွေကို ဆောင်းပါးနဲ့ အောက်ဆုံးမှာ ယူလို့ ရပါတယ်။
gitlab-ci-deploy-with-codedeploy
gitlab-ci-manage-app-zip
gitlab-ci-read-bucket
gitlab-ci-autoscaling
IAM >> Users ထဲမှာ Add user ကို နှိပ်ပြီး user အသစ်လုပ်ပါမယ်။ ဒီဆောင်းပါးမှာ user name ကို
gitlab-ci-user
လို့ သုံးပါမယ်။ Attach policies directly ကိုရွေးပြီးတော့gitlab-ci-deploy-with-codedeploy
နဲ့gitlab-ci-manage-app-zip
policy တွေ ကို ဒီ user မှာ attach တွဲပေးလိုက်ပါ။IAM >> Roles ထဲမှာ EC2 (Service Role) တစ်ခု ပြုလုပ်ပါ။
gitlab-ci-read-bucket
policy ကို attach တွဲပေးပြီး Role name ကိုgitlab-ci-ec2-role
လို့ ပေးလိုက်ပါ။IAM >> Roles ထဲမှာ CodeDeploy (Service Role) တစ်ခု ပြုလုပ်ပါ။
AWSCodeDeployRole
ဆိုတဲ့ default policy နဲ့gitlab-ci-autoscaling
policy ကို attach တွဲပေးပြီး Role name ကိုgitlab-ci-codedeploy-role
လို့ ပေးလိုက်ပါ။
Setup EC2
EC2 Console ထဲသွားပြီးတော့ Application Load Balancer တစ်ခု ဆောက်ပါ။ ALB name ကို
laravel-alb
လို့ ပေးလိုက်ပါမယ်။ ALB ဆောက်တဲ့အချိန်မှာ Listeners and routing ထဲမှာ Default action အတွက် Target Group ပါ ဆောက်ဖို့ လိုပါလိမ့်မယ်။ Traget Group Name ကို LaravelTG လို့ ပေးလိုက်ပါမယ်။ TG ထဲမှာ Health check point ထည့်ခိုင်းတဲ့အခါမှာ ကိုယ့် app မှာ healthCheck point ပါရင် ထည့်ပေးပါ မပါရင်တော့/
ပဲ ထည့်လိုက်ပါ။ (Health Check point က ပါသင့်ပါတယ်။ ဒါမှ service တစ်ခုခု ဖြစ်တာနဲ့ ALB က တန်းသိနိုင်မှာပါ။ ကျွန်တော်ကတော့ HealthCheck အတွက် Laravel-health ကိုသုံးပါတယ်။)ပြီးသွားရင် EC2 Console မှာပဲ Launch Template လုပ်ပါမယ်။ Name ကို
LaravelLT
လို့ ပေးလိုက်ပါမယ်။Name: LaravelLT
AMI: Amazon Linux 2 AMI
Advanced Detail >> IAM instance profile:
gitlab-ci-ec2-role
Advanced Detail >> User data: ဒီဆောင်းပါးရဲ့ အောက်ဆုံးမှာ UserData ကိုရေးပေးထားပါမယ်။
ကျန်တဲ့ အချက်အလက်တွေကိုတော့ ကိုယ် လိုချင်သလို ရွေးချယ်လို့ရပါတယ်။ Instant ထဲကို ssh login ဝင်ဖို့ အစီအစဉ်ရှိရင်တော့ network မှာ interface ထည့်ပြီး public IP auto ပေးဖို့ ရွေးခဲ့ပါ။ Security Group လည်း တစ်ခုရွေးခဲ့ဖို့လိုပါတယ်။
EC2 Consonsole ထဲမှာပဲ Auto Scaling Group တစ်ခု ဆောက်ပါမယ်။ Name ကိုတော့
LaravelASG
လို့ ပေးလိုက်ပါမယ်။ Launch Template ကLaravelLT
ကိုရွေးပြီး။ ALB ကိုတော့laravel-alb
ကို ရွေးပေးလိုက်ပါ။ ဒီအဆင့်မှာ ကိုယ်သုံးချင်တဲ့ instance size တွေ storage size တွေ ရွေးပေးဖို့ လိုပါတယ်။ Auto Scaling Group ရဲ့ နောက်ဆုံးအဆင့် Tag ထဲမှာ Name tage create လုပ်ပြီးLaravelASG-instance
လို့ပေးခဲ့လိုက်ပါ။ Instant တွေပြန်ကြည့်တဲ့အခါ ဒီ Group ထဲကမှန်း သိရတာပေါ့။
Creating CodeDeploy App and Group
CodeDeploy console ထဲမှာ code deploy app အသစ် ဆောက်ပါမယ်။ name ကို
LaravelCodeDeploy
လို့ ပေးလိုက်ပါမယ်။ Compute platform မှာ EC2/On-premises ကိုရွေးပါ။LaravelCodeDeploy
app ထဲမှာ codedeploy group ဆောက်ပါမယ်။ group name ကိုLaravelCodeDeploy
လို့ ပဲ ထပ်ပေးလိုက်ပါ။ Group ဆောက်တဲ့အခါမှာ။Deployment Group Name: LaravelCodeDeploy
Service role:
gitlab-ci-codedeploy-role
Deployment type: Blue/green
Environment configuration:
Automatically copy Amazon EC2 Auto Scaling group
Amazon EC2 Auto Scaling group:
LaravelASG
Load balancer:
laravel-alb
စတဲ့ အရှေ့ပိုင်းမှာ လုပ်ခဲ့တာတွေကို ပြန်ရွေးပေးရမှာ ဖြစ်ပါတယ်။
Setting Up in Gitlab repository
git repository ထဲမှာ
appspec.yml
,devops/prepare.sh
,devops/setup-app.sh
နဲ့.gitlab-ci.yml
file တွေ ထည့်ပါမယ်။ ဒီဖိုင်တွေကို အောက်ဆုံးမှာ ဖေါ်ပြပေးထားပါတယ်။ ကိုယ့် project နဲ့ အဆင်ပြေသလို code တွေကို ပြင်သုံးဖို့ လိုပါမယ်။ အကုန်ထည့်ပြီးရင် gitlab ပေါ်ကို push ပေးထားပါ။ဒီ Environment variables တွေကို Gitlab repository ထဲ ထည့်ပေးပါ။
AWS_ACCESS_KEY_ID:
gitlab-ci-user
ကနေ ထုတ်ရမှာပါ။AWS_SECRET_ACCESS_KEY:
gitlab-ci-user
ကနေ ထုတ်ရမှာပါ။AWS_REGION: အပေါ်က services တွေဆောက်ခဲ့တဲ့ region ကို ထည့်ပေးပါ။
AWS_CODEDEPLOY_APP:
LaravelCodeDeploy
AWS_CODEDEPLOY_GROUP:
LaravelCodeDeploy
AWS_CODEDEPLOY_S3_BUCKET:
kalaung-codedeploy
ဒါတွေအကုန်ပြီးပြီဆိုရင်တော့ pipe line ကို စမ်းပြီး run ကြည့်လို့ရပါပြီ။
Gitlab Pipeline ထဲမှာ pass ဖြစ်သွားရင် CodeDeploy dashboard မှာ သွားကြည့်ပါ။ Deployment run နေတာကိုမြင်ရပါမယ်။
Deployment မှာ AllowTraffic အဆင့်က ကြာတတ်ပါတယ် အကုန်ပြီးသွားရင်တော့။ repo ထဲက latest app က autoscaling group ထဲမှာ runသွားပြီဖြစ်ပါတယ်။
Auto scaling group အသစ်မှာ run နေတာ error မရှိဘူးဆိုရင် နောက် တစ်နာရီကြာတဲ့အခါမှာ Auto scaling group အဟောင်းကို CodeDeploy က ဖျက်လိုက်ပါလိမ့်မယ်။ ဖျက်ဖို့ စောင့်တဲ့ အချိန်ကို ကိုယ့်စိတ်ကြိုက် သတ်မှတ်ပေးလို့ရပါတယ်။
CodeDeploy သုံးထားတဲ့ အားသာချက်က AutoScalingGroup မှာ capacity တိုးလာတာနဲ့မျှ တိုးလာတဲ့ Instance အသစ်တွေမှာ လက်ရှိ deployment version ကို CodeDeploy agent က တဆင့် auto deploy ပေးသွားမှာဖြစ်ပါတယ်။
Required configs and files
php-fpm.conf
[www]
user = apache
group = apache
listen = /run/php-fpm/www.sock
listen.acl_users = apache,nginx
listen.allowed_clients = 127.0.0.1
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
slowlog = /var/log/php-fpm/www-slow.log
php_admin_value[error_log] = /var/log/php-fpm/www-error.log
php_admin_flag[log_errors] = on
php_value[session.save_handler] = files
php_value[session.save_path] = /var/lib/php/session
php_value[soap.wsdl_cache_dir] = /var/lib/php/wsdlcache
production.nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 4096;
include /etc/nginx/mime.types;
default_type application/octet-stream;
include /etc/nginx/conf.d/*.conf;
server {
listen 80;
listen [::]:80;
server_name _;
root /var/www/app/public;
include /etc/nginx/default.d/*.conf;
index index.php index.html index.html
disable_symlinks off;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options nosniff;
add_header Allow "GET, POST, HEAD" always;
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
send_timeout 600;
client_max_body_size 2048M;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
charset utf-8;
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php-fpm/www.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
}
}
gitlab-ci-deploy-with-codedeploy
Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"codedeploy:RegisterApplicationRevision",
"codedeploy:CreateDeployment",
"codedeploy:GetDeploymentConfig",
"codedeploy:GetApplicationRevision"
],
"Resource": [
"*"
]
}
]
}
gitlab-ci-manage-app-zip
Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::kalaung-codedeploy/packages/*"
]
}
]
}
gitlab-ci-read-bucket
Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:Get*",
"s3:List*"
],
"Resource": [
"arn:aws:s3:::kalaung-codedeploy",
"arn:aws:s3:::kalaung-codedeploy/*"
]
}
]
}
gitlab-ci-autoscaling
Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"iam:PassRole",
"ec2:CreateTags",
"ec2:RunInstances",
"autoscaling:*"
],
"Resource": "*"
}
]
}
userdata
to use in Launch Template
#!/bin/bash
yum update -y
yum install -y ruby wget curl zip unzip
yum install -y amazon-linux-extras
amazon-linux-extras enable php8.1
yum clean metadata
amazon-linux-extras install -y php8.1
yum install -y php-mbstring php-xml php-gd php-zip php-bcmath php-pgsql php-posix php-sodium
yum remove -y httpd
amazon-linux-extras install -y nginx1
amazon-linux-extras install -y epel
wget https://aws-codedeploy-ap-southeast-1.s3.amazonaws.com/latest/install
chmod +x install
./install auto
aws s3 cp s3://kalaung-codedeploy/production.nginx.conf /etc/nginx/nginx.conf
aws s3 cp s3://kalaung-codedeploy/php-fpm.conf /etc/php-fpm.d/www.conf
systemctl enable nginx
systemctl enable php-fpm
systemctl start nginx
systemctl start php-fpm
usermod -a -G apache ec2-user
appspec.yml
# Definition file for AWS CodeDeploy
version: 0.0
os: linux
files:
- source: /
destination: /var/www/app
permissions:
- object: /var/www/app
owner: ec2-user
group: apache
type:
- file
- directory
hooks:
BeforeInstall:
- location: devops/prepare.sh
AfterInstall:
- location: devops/setup-app.sh
devops/prepare.sh
# Prepares for the deployment. Called from CodeDeploy's appspec.yml.
touch /tmp/deployment-started
# Download the .env file from S3 before emptying the directory to shave
# off a few seconds of downtime in case we don't deregister the instance
# from the load balancer.
aws s3 cp s3://kalaung-codedeploy/production.env /tmp/production.env
# aws s3 cp s3://kalaung-codedeploy/oauth-private.key /tmp/oauth-private.key
# aws s3 cp s3://kalaung-codedeploy/oauth-public.key /tmp/oauth-public.key
# Completely empty the app directory before dumping the revision's files
# there to avoid any deployment failures.
rm -Rf /var/www/app/
mkdir /var/www/app/
chown ec2-user:apache /var/www/app/
touch /tmp/deployment-cleared
devops/setup-app.sh
# Set up Laravel after main deployment. Called from CodeDeploy's
# appspec.yml.
# Move the previously downloaded .env file to the right place.
mv /tmp/production.env /var/www/app/.env
# mv /tmp/oauth-public.key /var/www/app/storage/oauth-public.key
# mv /tmp/oauth-private.key /var/www/app/storage/oauth-private.key
# Create the storage directories.
sudo mkdir -p /var/www/app/bootstrap/cache
sudo mkdir -p /var/www/app/storage/app/public
sudo mkdir -p /var/www/app/storage/logs
sudo mkdir -p /var/www/app/storage/framework/{sessions,views,cache}
# Run new migrations. While this is run on all instances, only the
# first execution will do anything. As long as we're using CodeDeploy's
# OneAtATime configuration we can't have a race condition.
sudo php /var/www/app/artisan migrate --force
# Run production optimizations.
sudo php /var/www/app/artisan config:cache
sudo php /var/www/app/artisan optimize
sudo php /var/www/app/artisan route:cache
sudo chown -R apache:apache /var/www/app/storage
sudo chown -R ec2-user:apache /var/www/app/bootstrap/cache
sudo rm -fr /var/www/app/.git
# Reload php-fpm to clear OPcache.
sudo systemctl restart php-fpm
sudo systemctl restart nginx
touch /tmp/deployment-done
.gitlab-ci.yml
stages:
- deploy
image: lsfiege/laravel-php:8.1
before_script:
- apt-get install -y wget curl zip
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
- unzip awscliv2.zip
- ./aws/install
- rm -rf awscliv2.zip aws
production-deploy:
stage: deploy
script:
- cp .env.example .env
- composer install --no-interaction --quiet --no-scripts --prefer-dist
- aws configure set default.region ${AWS_REGION}
- aws deploy push --application-name ${AWS_CODEDEPLOY_APP} --s3-location s3://${AWS_CODEDEPLOY_S3_BUCKET}/packages/${CI_COMMIT_SHA}.zip --ignore-hidden-files
- aws deploy create-deployment --application-name ${AWS_CODEDEPLOY_APP} --s3-location bucket=${AWS_CODEDEPLOY_S3_BUCKET},key=packages/${CI_COMMIT_SHA}.zip,bundleType=zip --deployment-group-name ${AWS_CODEDEPLOY_GROUP}
when: manual
only:
- master