4
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

php-fpmで動くLaravelのdocker imageをgithub actions使ってECRへデプロイ

Last updated at Posted at 2019-12-13

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な情報は引き数で入れる方針で。

docker-compose.yml
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が書き込まれないので上書き

docker/php-fpm.d/zz-docker.conf
[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に指定

docker/nginx/conf.d/default.conf
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_KEYSECRET_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ファイルをコンテナ間で共有

task-definition/app.tf
[
    {
        "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"
    }
]

削除は単純なポリシーで

ecr/lifecycle-policy.json
{
    "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* とつけたらデプロイされるようにしてます。

.github/workflows/deploy.yml
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からタグ打てばデプロイされて便利です。

4
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?