laravelのアプリをphp-fpmでsocket使ってgithub actionsでECSにデプロイしたのでメモ。
php&laravel知識は私にはほぼないです。DB使ってないアプリなのでDB設定なし。
雑において使えればよかったのでいろいろ適当です。
だったらphp-fpmにする必要もなかったんですができるのかなと思ってやったら出来たので記録。
前提
- Docker
- docker-compose
- デプロイできるAWS ECSクラスタが存在する
- AWS ECR
- github actions
- Terraform
dockerの作成
ローカルで確認できるように&pushするときのタグを保存できるのでdocker-compose.ymlでbuildするようにする。
環境変数で基本必要なものを .env.docker
に作っておいてsecretな情報は引き数で入れる方針で。
version: '3.7'
volumes:
sock:
name: sock
services:
nginx:
image: 012345678901.dkr.ecr.us-east-1.amazonaws.com/app-nginx
container_name: app-nginx
build:
context: .
dockerfile: ./docker/nginx/Dockerfile
volumes:
- sock:/sock
depends_on:
- php
ports:
- "8080:80"
php:
image: 012345678901.dkr.ecr.us-east-1.amazonaws.com/app-php
container_name: app-php
build:
context: .
args:
SECRET_TOKEN: "$SECRET_TOKEN"
volumes:
- sock:/sock
php-fpm
FROM php:7.4-fpm-alpine
ENV COMPOSER_ALLOW_SUPERUSER 1
ARG SECRET_TOKEN
COPY --from=composer /usr/bin/composer /usr/bin/composer
RUN apk add --no-cache zip unzip
WORKDIR /code
COPY --chown=www-data:www-data . .
RUN composer install
COPY .env.docker .env
COPY docker/php-fpm.d /usr/local/etc/php-fpm.d
RUN echo SECRET_TOKEN="$SECRET_TOKEN" >> .env
RUN php artisan key:generate
RUN chown www-data /usr/local/var/log/
WORKDIR /code/public
php-fpmのコンテナだとzz-docker.confにlistenが入っているので上書きしないとsocketが書き込まれないので上書き
[global]
daemonize = no
[www]
listen = /sock/php-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0666
他php-fpmで変えたい項目があるなら、 www.conf
も上書きすればよい
nginx
public/
にあるファイルは必要なので配置
FROM nginx:stable-alpine
ADD docker/nginx/conf.d/ /etc/nginx/conf.d
ADD public/ /code/public
EXPOSE 80
STOPSIGNAL SIGTERM
CMD ["nginx", "-g", "daemon off;"]
/sock/php-fpm.sock
でボリューム共有するのでそこをfastcgi_passに指定
server {
listen 80 default_server;
server_name _;
charset utf-8;
client_max_body_size 75M;
gzip on;
gzip_types text/plain application/xml text/css application/javascript;
gzip_min_length 1000;
index index.php index.html;
root /code/public;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/sock/php-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}
下記実行して 127.0.0.1:8080
がひらけたら成功
docker-compose build
docker-compose up
TerraformでECR と ECS service用意
networkModeをawsvpcにしてdiscovery serviceで参照する設定いれてます。
security group、vpcとdiscovery serviceの設定は別のところでやっているとして省略。
github actions用のユーザterraformで作って ACCESS_KEY
と SECRET_ACCESS_KEY
はtfstateにいれたくないからWebコンソールからとってます。
resource "aws_cloudwatch_log_group" "app" {
name = "/app"
retention_in_days = 60
tags = {
Environment = "app"
}
}
resource "aws_ecs_task_definition" "app" {
family = "app"
volume {
name = "sock"
docker_volume_configuration {
scope = "task"
driver = "local"
}
}
container_definitions = file("task-definition/app.json")
network_mode = "awsvpc"
}
resource "aws_ecs_service" "app" {
name = "app"
cluster = "cluster"
task_definition = aws_ecs_task_definition.app.arn
desired_count = 1
deployment_minimum_healthy_percent = 0
deployment_maximum_percent = 100
network_configuration {
subnets = aws_subnet.vpc.*.id
security_groups = [
aws_security_group.output.id
aws_security_group.app.id
]
}
service_registries {
registry_arn = aws_service_discovery_service.app.arn
}
lifecycle {
ignore_changes = [desired_count]
}
}
resource "aws_service_discovery_service" "app" {
name = "app"
dns_config {
namespace_id = aws_service_discovery_public_dns_namespace.main.id
dns_records {
ttl = 10
type = "A"
}
routing_policy = "MULTIVALUE"
}
health_check_custom_config {
failure_threshold = 1
}
}
# ECR
resource "aws_ecr_repository" "app-nginx" {
name = "app-nginx"
}
resource "aws_ecr_repository" "app-php" {
name = "app-php"
}
resource "aws_ecr_lifecycle_policy" "app-nginx" {
repository = aws_ecr_repository.app-nginx.name
policy = file("ecr/lifecycle-policy.json")
}
resource "aws_ecr_lifecycle_policy" "app-php" {
repository = aws_ecr_repository.app-php.name
policy = file("ecr/lifecycle-policy.json")
}
resource "aws_iam_user" "github-actions-app" {
name = "github-actions-app"
path = "/"
}
resource "aws_iam_user_policy" "github-actions-app" {
name = "github-actions-app"
user = "${aws_iam_user.github-actions-app.name}"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"ecs:UpdateService",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:CompleteLayerUpload",
"ecr:DescribeRepositories",
"ecr:UploadLayerPart",
"ecr:InitiateLayerUpload",
"ecr:BatchCheckLayerAvailability",
"ecr:PutImage"
],
"Resource": [
"arn:aws:ecs:us-east-1:012345678901:service/cluster/app",
"arn:aws:ecr:us-east-1:012345678901:repository/app-*"
]
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": "ecr:GetAuthorizationToken",
"Resource": "*"
}
]
}
EOF
}
volumeをmountしてsocketファイルをコンテナ間で共有
[
{
"mountPoints": [
{
"sourceVolume": "sock",
"containerPath": "/sock"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/app",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "nginx"
}
},
"cpu": 32,
"memoryReservation": 32,
"image": "012345678901.dkr.ecr.us-east-1.amazonaws.com/app-nginx:latest",
"portMappings": [
{
"hostPort": 80,
"containerPort": 80,
"protocol": "tcp"
}
],
"name": "nginx"
},
{
"mountPoints": [
{
"sourceVolume": "sock",
"containerPath": "/sock"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/app",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "php"
}
},
"cpu": 64,
"memoryReservation": 64,
"image": "012345678901.dkr.ecr.us-east-1.amazonaws.com/app-php:latest",
"name": "php"
}
]
削除は単純なポリシーで
{
"rules": [
{
"rulePriority": 1,
"description": "Expire images older than 7 count",
"selection": {
"tagStatus": "untagged",
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": 7
},
"action": {
"type": "expire"
}
}
]
}
imageがない状態で terraform apply
するとECS Serviceが起動でエラーでますが気にしない。
するなら、ECRのところだけ terraform apply
して docker-compose push
してからやるとECS Serviceも起動する。
github actions
githubのsecretsに AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
SECRET_TOKEN
を作ってます。
自動でデプロイされたくないので tagに v*
とつけたらデプロイされるようにしてます。
name: Deploy
on:
push:
tags:
- v*
jobs:
deploy_production:
name: deploy production
runs-on: ubuntu-latest
steps:
- name: AWS ECR login
run: $(aws ecr get-login --no-include-email)
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
- uses: actions/checkout@master
- name: Get Composer Cache Directory
id: composer-cache
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
- uses: actions/cache@v1
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-
- name: build base
run: docker-compose build
env:
SECRET_TOKEN: ${{ secrets.SECRET_TOKEN }}
- name: push image
run: docker-compose push
- name: force deployment ecr
run: aws ecs update-service --cluster cluster --service app --force-new-deployment
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
これでgithubでreleaseからタグ打てばデプロイされて便利です。