Rails アプリを GitHub Actions で AWS ECS Fargate に自動デプロイする

はじめに

対象: Rails アプリケーション / Amazon ECR / Amazon ECS Fargate / RDS PostgreSQL / GitHub Actions
目的: mainブランチにマージされたら、GitHub Actions から Rails アプリを AWS ECS Fargate に自動デプロイし、デプロイ前にrails db:migrate` を実行する。
今回実施した作業内容を、今後の参考のために備忘録としてまとめています。


この手順で実現すること

Pull Request を main に merge
        ↓
GitHub Actions が main への push を検知
        ↓
Docker image を build
        ↓
Amazon ECR に push
        ↓
新しい image を使う ECS task definition を作成
        ↓
ECS Fargate の一時 task で rails db:migrate を実行
        ↓
migrate 成功時だけ ECS Service を更新
        ↓
ALB 配下の Rails アプリが新バージョンに切り替わる

この手順では、AWS access key / secret access key を GitHub に保存せず、GitHub OIDC + IAM Role で AWS に接続する。


前提

既存の手動デプロイ手順で、以下のような AWS 構成が作成済みであること。

Route53 → ALB → ECS Fargate → RDS PostgreSQL
ECS → S3
ECS → SES
手動デプロイの流れ
ローカルファイル修正 → ECRにpush → タスク定義作成 → サービス更新
※サービス更新時、新しいデプロイの強制のチェックボックスにチェック

今回の例では、元の手動デプロイ記事に合わせて以下の名前を使う。

AWS_REGION: ap-northeast-1
ECR_REPOSITORY: twitterclone
ECS_CLUSTER: twitterclone-ecs-cluster
ECS_CONTAINER_NAME: twitterclone-web

実際の環境で名前が違う場合は、自分の AWS コンソールに表示されている値に置き換える。


1. AWS コンソールで最初に控える値

自動デプロイの workflow を書く前に、AWS コンソールから必要な値を控える。

1-1. リージョンを確認する

AWS コンソール右上のリージョンを確認する。

東京リージョンで作成している場合は以下。

ap-northeast-1

以降、ECR / ECS / RDS / ALB を確認するときは、必ず同じリージョンを選択する。


1-2. AWS アカウント ID を確認する

AWS コンソール右上のアカウント名またはアカウントメニューを開く。

控える値:

AWS_ACCOUNT_ID = 12桁の AWS アカウント ID

後で IAM Policy や IAM Role の ARN に使う。


1-3. ECR repository 名と URI を確認する

AWS コンソールで以下へ進む。

Amazon ECR
  → Private registry
  → Repositories
  → twitterclone

控える値:

ECR_REPOSITORY = twitterclone
ECR Repository URI = <AWS_ACCOUNT_ID>.dkr.ecr.ap-northeast-1.amazonaws.com/twitterclone

もし twitterclone repository がまだ無い場合は、以下で作成する。

Amazon ECR
  → Private registry
  → Repositories
  → Create repository

設定例:

Visibility settings: Private
Repository name: twitterclone
Image tag mutability: Mutable で可
Encryption configuration: AES-256 で可

今回の GitHub Actions では latest 固定ではなく Git の commit SHA を image tag にする。


1-4. ECS cluster / service / task definition / container 名を確認する

AWS コンソールで以下へ進む。

Amazon ECS
  → Clusters
  → twitterclone-ecs-cluster

Services に表示されている service 名を控える。

ECS_CLUSTER = twitterclone-ecs-cluster
ECS_SERVICE = AWS コンソールに表示されている service 名

次に Task Definition を確認する。

Amazon ECS
  → Task definitions
  → 対象の task definition family

控える値:

ECS_TASK_DEFINITION_FAMILY = task definition family 名
ECS_CONTAINER_NAME = twitterclone

例:

twitterclone:12

上記のように表示されている場合、family 名は twitterclone


1-5. ECS Service の subnet / security group を確認する

GitHub Actions で rails db:migrate を実行する一時 Fargate task は、ECS Service と同じ VPC / subnet / security group で動かす。

AWS コンソールで以下へ進む。

Amazon ECS
  → Clusters
  → twitterclone-ecs-cluster
  → Services
  → 対象 service をクリック
  → Configuration または Networking

控える値:

VPC ID
Subnet IDs
Security Group IDs
Public IP が ON / OFF のどちらか

元の構成どおり Public Subnet ×2 / Public IP ON で動かしている場合、GitHub Secrets には以下のような値を入れる。

ECS_SUBNET_IDS = subnet-xxxxxxxx,subnet-yyyyyyyy
ECS_SECURITY_GROUP_IDS = sg-xxxxxxxx
ECS_ASSIGN_PUBLIC_IP = ENABLED

ECS_SUBNET_IDSECS_SECURITY_GROUP_IDS は、カンマ区切り・スペースなしで控える。


1-6. RDS security group を確認する

migration task が RDS に接続できる必要がある。

AWS コンソールで以下へ進む。

EC2
  → Security Groups
  → RDS 用 security group
  → Inbound rules

以下の inbound rule があることを確認する。

Type: PostgreSQL
Port: 5432
Source: ECS 用 security group

例:

RDS Security Group: twitterclone-rds-sg
Inbound rule:
  PostgreSQL / 5432 / Source: twitterclone-ecs-sg

2. IAM OIDC Provider を作る

GitHub Actions から AWS に入るために、IAM の OIDC Provider を作成する。

AWS コンソールで以下へ進む。

IAM
  → Access management
  → Identity providers
  → Add provider

入力内容:

Provider type: OpenID Connect
Provider URL: https://token.actions.githubusercontent.com
Audience: sts.amazonaws.com

入力したら Add provider をクリックする。

これにより、GitHub Actions が AWS の IAM Role を引き受けるための入口ができる。


3. GitHub Actions 用 IAM Policy を作る

GitHub Actions が ECR / ECS を操作できるように、IAM Policy を作成する。

AWS コンソールで以下へ進む。

IAM
  → Access management
  → Policies
  → Create policy
  → JSON

以下の JSON を貼り付ける。

置き換える値:

<AWS_ACCOUNT_ID>              自分の AWS アカウント ID
<ECS_SERVICE_NAME>            ECS Service 名
<TASK_FAMILY>                 ECS Task Definition family 名
<TASK_EXECUTION_ROLE_NAME>    ECS Task Execution Role 名
<TASK_ROLE_NAME>              ECS Task Role 名

IAM Policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ECRAuthorization",
      "Effect": "Allow",
      "Action": ["ecr:GetAuthorizationToken"],
      "Resource": "*"
    },
    {
      "Sid": "ECRPushImage",
      "Effect": "Allow",
      "Action": [
        "ecr:BatchCheckLayerAvailability",
        "ecr:BatchGetImage",
        "ecr:CompleteLayerUpload",
        "ecr:DescribeImages",
        "ecr:DescribeRepositories",
        "ecr:GetDownloadUrlForLayer",
        "ecr:InitiateLayerUpload",
        "ecr:PutImage",
        "ecr:UploadLayerPart"
      ],
      "Resource": "arn:aws:ecr:ap-northeast-1:<AWS_ACCOUNT_ID>:repository/twitterclone"
    },
    {
      "Sid": "ECSRegisterTaskDefinition",
      "Effect": "Allow",
      "Action": ["ecs:DescribeTaskDefinition", "ecs:RegisterTaskDefinition"],
      "Resource": "*"
    },
    {
      "Sid": "ECSDeployService",
      "Effect": "Allow",
      "Action": ["ecs:DescribeServices", "ecs:UpdateService"],
      "Resource": "arn:aws:ecs:ap-northeast-1:<AWS_ACCOUNT_ID>:service/twitterclone-ecs-cluster/<ECS_SERVICE_NAME>"
    },
    {
      "Sid": "ECSRunMigrationTask",
      "Effect": "Allow",
      "Action": ["ecs:RunTask"],
      "Resource": "arn:aws:ecs:ap-northeast-1:<AWS_ACCOUNT_ID>:task-definition/<TASK_FAMILY>:*",
      "Condition": {
        "ArnEquals": {
          "ecs:cluster": "arn:aws:ecs:ap-northeast-1:<AWS_ACCOUNT_ID>:cluster/twitterclone-ecs-cluster"
        }
      }
    },
    {
      "Sid": "ECSDescribeMigrationTask",
      "Effect": "Allow",
      "Action": ["ecs:DescribeTasks"],
      "Resource": "*"
    },
    {
      "Sid": "PassEcsTaskRoles",
      "Effect": "Allow",
      "Action": ["iam:PassRole"],
      "Resource": [
        "arn:aws:iam::<AWS_ACCOUNT_ID>:role/<TASK_EXECUTION_ROLE_NAME>",
        "arn:aws:iam::<AWS_ACCOUNT_ID>:role/<TASK_ROLE_NAME>"
      ],
      "Condition": {
        "StringEquals": {
          "iam:PassedToService": "ecs-tasks.amazonaws.com"
        }
      }
    }
  ]
}

貼り付け後、以下のように進める。

Next
  → Policy name
  → twitterclone-github-actions-deploy-policy
  → Create policy

Task execution role / Task role の確認場所

AWS コンソールで以下へ進む。

Amazon ECS
  → Task definitions
  → 対象 family
  → 最新 revision
  → Task roles

よくある例:

Task execution role: ecsTaskExecutionRole
Task role: ecs-task-s3-role

S3 や SES を Rails アプリから使っている場合は、既存の Task Role を iam:PassRole の対象に含める。


4. GitHub Actions 用 IAM Role を作る

GitHub Actions が assume する IAM Role を作成する。

AWS コンソールで以下へ進む。

IAM
  → Access management
  → Roles
  → Create role

4-1. Trusted entity を選ぶ

以下を選択する。

Trusted entity type: Web identity
Identity provider: token.actions.githubusercontent.com
Audience: sts.amazonaws.com

画面に GitHub 用の入力欄が表示される場合は、以下も入力する。

GitHub organization: GitHub の owner 名
GitHub repository: Rails アプリの repository 名
GitHub branch: main

表示されない場合でも、Role 作成後に Trust policy を直接編集するので問題ない。


4-2. 作成した IAM Policy を attach する

Permission policy の選択画面で、先ほど作った policy を検索する。

TwitterCloneGitHubActionsEcsDeployPolicy

チェックを入れて Next に進む。


4-3. Role name を付ける

Role name:

TwitterCloneGitHubActionsEcsDeployRole

Create role をクリックする。


4-4. Trust policy を確認・修正する

Role 作成後、以下へ進む。

IAM
  → Roles
  → TwitterCloneGitHubActionsEcsDeployRole
  → Trust relationships
  → Edit trust policy

以下の形になっているか確認する。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::<AWS_ACCOUNT_ID>:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
          "token.actions.githubusercontent.com:sub": "repo:<GITHUB_OWNER>/<GITHUB_REPO>:ref:refs/heads/main"
        }
      }
    }
  ]
}

置き換える値:

<AWS_ACCOUNT_ID>  AWS アカウント ID
<GITHUB_OWNER>   GitHub の owner 名
<GITHUB_REPO>    GitHub repository 名

例:

"token.actions.githubusercontent.com:sub": "repo:yourname/twitterclone:ref:refs/heads/main"

これにより、対象 repository の main ブランチの workflow だけが、この IAM Role を assume できる。

保存後、Role の Summary 画面に戻って ARN をコピーする。

arn:aws:iam::<AWS_ACCOUNT_ID>:role/twitterclone-github-actions-deploy-role

この ARN は GitHub Secret の AWS_ROLE_TO_ASSUME に登録する。


5. ECS Task Definition の環境変数を確認する

GitHub Actions の migration task は、既存の ECS Task Definition を使って実行する。

そのため、Task Definition に本番 Rails が起動できる環境変数が入っている必要がある。

AWS コンソールで以下へ進む。

Amazon ECS
  → Task definitions
  → 対象 task definition family
  → 最新 revision
  → Container definitions
  → twitterclone
  → Environment variables

必要な環境変数の例:

RAILS_ENV=production
DATABASE_HOST=<RDS Endpoint>
MYAPP_DATABASE_PASSWORD=<password>
SECRET_KEY_BASE=<rails secret>
RAILS_SERVE_STATIC_FILES=true

これらが Task Definition に入っていれば、GitHub Actions 側に DB password や SECRET_KEY_BASE を直接入れる必要はない。

ただし、本番 DB パスワードや SECRET_KEY_BASE を平文の environment variables に置くより、ECS の Secrets から AWS Secrets Manager または SSM Parameter Store を参照する構成の方が安全。


6. ECS Console で rails db:migrate の Run Task を手動確認する

自動化する前に、AWS コンソールから一度 rails db:migrate を手動実行して、ネットワークや環境変数が正しいことを確認する。

AWS コンソールで以下へ進む。

Amazon ECS
  → Clusters
  → twitterclone-ecs-cluster
  → Tasks tab
  → Run task

設定例:

Existing cluster: twitterclone-ecs-cluster
Compute configuration: Launch type
Launch type: FARGATE
Platform version: LATEST
Task definition: twitterclone:<latest revision>
Desired tasks: 1

Networking は ECS Service と同じにする。

VPC: ECS Service と同じ VPC
Subnets: ECS Service と同じ subnet
Security group: ECS Service と同じ security group
Public IP: ECS Service と同じ設定

元の構成どおり Public Subnet / Public IP ON の場合は、Public IP を Turned on または Enabled にする。

次に、画面下の方にある以下を開く。

Container Overrides
  → twitterclone
  → Command override

Command override に以下を入力する。

bundle,exec,rails,db:migrate

最後に Create または Run task をクリックする。

実行後、以下で task の結果を確認する。

Amazon ECS
  → Clusters
  → twitterclone-ecs-cluster
  → Tasks
  → Desired task status: Stopped
  → 該当 task をクリック

成功時の目安:

Last status: STOPPED
Exit code: 0

ここで成功していれば、GitHub Actions の自動 migrate でも同じ subnet / security group / task definition を使うことで成功しやすい。


7. ECS Service の手動更新方法も確認しておく

自動化後は GitHub Actions が ECS Service を更新するが、AWS コンソールでは以下の操作に相当する。

Amazon ECS
  → Clusters
  → twitterclone-ecs-cluster
  → Services
  → 対象 service にチェック
  → Update
  → Task definition で新 revision を選択
  → Force new deployment
  → Update

GitHub Actions では、この作業を以下の action が代行する。

aws-actions/amazon-ecs-deploy-task-definition@v2

8. GitHub に Secrets を登録する

ここからは GitHub 側の GUI 作業。

GitHub repository で以下へ進む。

GitHub repository
  → Settings
  → Secrets and variables
  → Actions
  → Secrets
  → New repository secret

登録する secret:

AWS_ROLE_TO_ASSUME
ECS_SUBNET_IDS
ECS_SECURITY_GROUP_IDS

値の例:

AWS_ROLE_TO_ASSUME=arn:aws:iam::<AWS_ACCOUNT_ID>:role/twitterclone-github-actions-deploy-role
ECS_SUBNET_IDS=subnet-aaaaaaaa,subnet-bbbbbbbb
ECS_SECURITY_GROUP_IDS=sg-cccccccc

注意:

ECS_SUBNET_IDS はカンマ区切り、スペースなし
ECS_SECURITY_GROUP_IDS もカンマ区切り、スペースなし

9. GitHub で main を default branch にする

GitHub repository で以下へ進む。

GitHub repository
  → Settings
  → General
  → Default branch
  → main を選択
  → Update

これで以下の仕様を満たす。

main ブランチを default branch とする

main への直接 push を避けたい場合は、Branch protection rule も設定する。

GitHub repository
  → Settings
  → Rules
  → Rulesets
  または
  → Settings
  → Branches
  → Branch protection rules

推奨設定:

Target branch: main
Require a pull request before merging: ON
Require approvals: ON
Require status checks to pass: 必要に応じて ON

10. GitHub Actions workflow を作成する

Rails アプリの repository に以下のファイルを作成する。

.github/workflows/deploy.yml

内容:

name: Deploy Rails to Amazon ECS Fargate

on:
  push:
    branches:
      - main
  workflow_dispatch:

permissions:
  id-token: write
  contents: read

concurrency:
  group: production-deploy
  cancel-in-progress: false

env:
  AWS_REGION: ap-northeast-1

  ECR_REPOSITORY: twitterclone

  ECS_CLUSTER: twitterclone-ecs-cluster
  ECS_SERVICE: <ECS_SERVICE_NAME>
  ECS_TASK_DEFINITION_FAMILY: <TASK_FAMILY>
  ECS_CONTAINER_NAME: twitterclone

  ECS_ASSIGN_PUBLIC_IP: ENABLED

jobs:
  deploy:
    name: Build, migrate, and deploy
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v7

      - name: Configure AWS credentials by OIDC
        uses: aws-actions/configure-aws-credentials@v6.1.0
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
          aws-region: ${{ env.AWS_REGION }}

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2

      - name: Build, tag, and push Docker image to Amazon ECR
        id: build-image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build --platform linux/amd64 \
            -t "$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" .

          docker push "$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"

          echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> "$GITHUB_OUTPUT"

      - name: Download current ECS task definition
        run: |
          aws ecs describe-task-definition \
            --task-definition "$ECS_TASK_DEFINITION_FAMILY" \
            --query taskDefinition > task-definition.json

          jq 'del(
            .taskDefinitionArn,
            .revision,
            .status,
            .requiresAttributes,
            .compatibilities,
            .registeredAt,
            .registeredBy
          )' task-definition.json > task-definition.cleaned.json

          mv task-definition.cleaned.json task-definition.json

      - name: Render ECS task definition with new image
        id: task-def
        uses: aws-actions/amazon-ecs-render-task-definition@v1
        with:
          task-definition: task-definition.json
          container-name: ${{ env.ECS_CONTAINER_NAME }}
          image: ${{ steps.build-image.outputs.image }}

      - name: Run rails db:migrate, then deploy to Amazon ECS
        uses: aws-actions/amazon-ecs-deploy-task-definition@v2
        with:
          task-definition: ${{ steps.task-def.outputs.task-definition }}
          cluster: ${{ env.ECS_CLUSTER }}
          service: ${{ env.ECS_SERVICE }}
          wait-for-service-stability: true
          wait-for-minutes: 30

          run-task: true
          wait-for-task-stopped: true
          run-task-launch-type: FARGATE
          run-task-subnets: ${{ secrets.ECS_SUBNET_IDS }}
          run-task-security-groups: ${{ secrets.ECS_SECURITY_GROUP_IDS }}
          run-task-assign-public-IP: ${{ env.ECS_ASSIGN_PUBLIC_IP }}
          run-task-container-overrides: |
            [
              {
                "name": "${{ env.ECS_CONTAINER_NAME }}",
                "command": ["bundle", "exec", "rails", "db:migrate"]
              }
            ]

置き換える箇所:

ECS_SERVICE: <ECS_SERVICE_NAME>
ECS_TASK_DEFINITION_FAMILY: <TASK_FAMILY>

例:

ECS_SERVICE: twitterclone-service
ECS_TASK_DEFINITION_FAMILY: twitterclone

workflow の重要ポイント

main に merge されたときだけ実行する。

on:
  push:
    branches:
      - main

OIDC を使うために id-token: write を設定する。

permissions:
  id-token: write
  contents: read

Docker image は commit SHA で tag 付けする。

IMAGE_TAG: ${{ github.sha }}

デプロイ前に migration task を実行する。

run-task: true
wait-for-task-stopped: true
run-task-container-overrides: |
  [
    {
      "name": "${{ env.ECS_CONTAINER_NAME }}",
      "command": ["bundle", "exec", "rails", "db:migrate"]
    }
  ]

これにより、rails db:migrate が成功した場合だけ ECS Service が新しい task definition に更新される。


11. 動作確認

11-1. Pull Request を main に merge する

作業 branch を作成する。

git checkout -b feature/test-auto-deploy

変更を commit する。

git add .
git commit -m "Test auto deploy to ECS"
git push origin feature/test-auto-deploy

GitHub で Pull Request を作成し、base branch を main にする。
Pull Request を merge する。


11-2. GitHub Actions を確認する

GitHub repository で以下へ進む。

Actions
  → Deploy Rails to Amazon ECS Fargate

以下の step が順番に成功することを確認する。

Configure AWS credentials by OIDC
Login to Amazon ECR
Build, tag, and push Docker image to Amazon ECR
Download current ECS task definition
Render ECS task definition with new image
Run rails db:migrate, then deploy to Amazon ECS

11-3. AWS コンソールで ECR を確認する

AWS コンソールで以下へ進む。

Amazon ECR
  → Private registry
  → Repositories
  → twitterclone
  → Images

github.sha の image tag が増えていることを確認する。

例:

1a2b3c4d5e6f...

11-4. AWS コンソールで migration task を確認する

AWS コンソールで以下へ進む。

Amazon ECS
  → Clusters
  → twitterclone-ecs-cluster
  → Tasks

Stopped task を表示し、GitHub Actions が実行した一時 task を開く。

確認する値:

Last status: STOPPED
Exit code: 0

Exit code: 0 なら rails db:migrate は成功。


11-5. AWS コンソールで ECS Service を確認する

AWS コンソールで以下へ進む。

Amazon ECS
  → Clusters
  → twitterclone-ecs-cluster
  → Services
  → 対象 service

確認する箇所:

Deployments
  → 新しい task definition revision になっている

Tasks
  → 新しい revision の task が RUNNING になっている

最後に ALB または Route53 のドメインで Rails アプリにアクセスし、画面が表示されることを確認する。


補足: 今回作成した GitHub Actions が AWS コンソール作業の何を自動化しているか

Login to Amazon ECR
  = ECR に docker push するための認証

Build, tag, and push Docker image
  = 手元で docker build / docker push していた作業

Download current ECS task definition
  = ECS → Task definitions → 最新 revision を確認する作業

Render ECS task definition with new image
  = ECS → Task definitions → Create new revision で Image URI を差し替える作業

Run rails db:migrate
  = ECS → Clusters → Tasks → Run task → Container Overrides で migrate 実行する作業

Deploy to Amazon ECS
  = ECS → Clusters → Services → Update → 新しい task definition revision を選ぶ作業

RailsアプリをAWSにデプロイする

はじめに

Docker 化した Ruby on Rails アプリケーション(Twitter Clone)を AWS ECS Fargate 上へ本番デプロイしました。 今回実施した作業内容を、今後の参考のために備忘録としてまとめています。


システム構成


使用サービス

  • Route53
  • ACM
  • VPC
  • ECS Fargate
  • ECR
  • ALB
  • RDS PostgreSQL
  • S3
  • SES
  • IAM

1. Route53 ドメイン取得

・DNSサービス
・ここでは、example.comをALB のIPへ変換する仕組み

  1. Route53を開く
  2. 登録済みドメイン
  3. ドメインを登録
  4. ドメイン購入
  5. Hosted Zone作成確認

2. ACM証明書作成

・SSL証明書とは、インターネット上で「通信の暗号化」と「サイト運営者の身元証明」を行うための電子証明書
・導入されているとURLが「http」から「https」に変わり、鍵マークが表示される

  1. AWS Certificate Manager
  2. 証明書をリクエスト
  3. パブリック証明書
  4. ドメイン入力
  5. DNS検証
  6. Route53レコード作成
  7. 発行済みになるまで待機

twitterclone.link
*.twitterclone.link

3. VPC作成

VPC名

twitterclone-vpc

作成内容

  • Public Subnet ×2
  • Private Subnet ×2
  • Internet Gateway

今回は ECS を Public Subnet で運用した。


4. Security Group作成

ALB

twitterclone-alb-sg
Type Port
HTTP 80
HTTPS 443

ECS

twitterclone-ecs-sg
Type Port Source
Custom TCP 3000 ALB SG

RDS

twitterclone-rds-sg
Type Port Source
PostgreSQL 5432 ECS SG

5. RDS PostgreSQL作成

DB識別子

twitterclone-rds

設定

Engine            PostgreSQL
Version           14.20-R2
DB Name           myapp_production
User              postgres
Public Access     No

理由

docker-compose が postgres:14-alpine のため
メジャーバージョンを合わせる。

6. ECR作成

AWS ECR(Amazon Elastic Container Registry)とは、Dockerなどのコンテナイメージを安全に保存・管理・共有するための倉庫

Repository

twitterclone

※ PostgreSQL 用の ECR は不要


7. Dockerイメージ作成・Push

docker build --platform linux/amd64 -t twitterclone .
docker tag twitterclone:latest <ECR_URI>:latest
docker push <ECR_URI>:latest

8. ECS Cluster作成

ECS(Amazon Elastic Container Service)における「クラスター」とは、コンテナ(アプリ)を実行するための仮想的な敷地や箱のこと

Cluster名

twitterclone-ecs-cluster

Infrastructure

AWS Fargate

9. Task Definition作成

設定

OS             Linux
Architecture   X86_64
CPU            0.5vCPU
Memory         1GB

Container

Name : twitterclone
Port : 3000

10. database.yml修正

発生したエラー

could not translate host name "db"

原因

Docker Compose 用ホスト名が本番でも利用されていた

修正

production:
  host: <%= ENV["DATABASE_HOST"] %>

環境変数

DATABASE_HOST=<RDS Endpoint>

11. ECS環境変数設定

RAILS_ENV=production
DATABASE_HOST=<RDS Endpoint>
MYAPP_DATABASE_PASSWORD=<password>
SECRET_KEY_BASE=<rails secret>
RAILS_SERVE_STATIC_FILES=true

SECRET_KEY_BASE生成

docker compose exec web bin/rails secret

12. ALB作成

名前

twitterclone-dev-alb

設定

Internet-facing
HTTP : 80
HTTPS : 443

ACM証明書を設定


13. Target Group作成

Target Type : IP
Port        : 3000

Health Check

/users/sign_in

Success Code

200-399

14. ECS Service作成

設定

Launch Type   : Fargate
Desired Count : 2

Network

Public Subnet ×2
Public IP     ON

Load Balancer

ALB
Target Group

15. Route53とALB接続

Hosted Zone

A Record
Alias = ON
ALB選択

確認

https://twitterclone.link

16. Seed投入

Seed

bundle,exec,rails,db:seed

Reset + Seed

bash,-lc,DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bundle exec rails db:migrate:reset && bundle exec rails db:seed

注意

ECS ServiceがDBへ接続中だと失敗する場合あり

17. S3設定(ActiveStorage)

Bucket

my-twitter-clone-bucket

storage.yml

amazon:
  service: S3
  region: ap-northeast-1
  bucket: my-twitter-clone-bucket

18. IAM設定

Task Role

ecs-task-s3-role

付与権限

AmazonS3FullAccess

19. Asset Pipeline対応

発生したエラー

AssetNotFound
x_logo.avif

Dockerfile

COPY . /myapp

RUN RAILS_ENV=production bundle exec rails assets:precompile

追加

imagemagick

ECS環境変数

RAILS_SERVE_STATIC_FILES=true

20. デフォルトアイコン設定

Userモデル

after_commit :attach_default_icon, on: :create

画像未設定時に自動添付する。


21. SES設定

Verified Identities

twitterclone.link
自分のメールアドレス

SMTP

email-smtp.ap-northeast-1.amazonaws.com

22. ActionMailer設定

default from: 'no-reply@twitterclone.link'

production.rb

config.action_mailer.default_url_options = {
  host: 'twitterclone.link',
  protocol: 'https'
}

23. SESエラー対応

エラー

554 Message rejected

原因

送信元アドレス未認証

対応

no-reply@twitterclone.link に統一

24. よく使うRun Taskコマンド

Migration

bundle,exec,rails,db:migrate

Seed

bundle,exec,rails,db:seed

Reset + Seed

bash,-lc,DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bundle exec rails db:migrate:reset && bundle exec rails db:seed

確認メール再送

bundle,exec,rails,runner,u=User.find_by(email:'xxxxx@gmail.com');u.send_confirmation_instructions

デプロイしたサイトにアクセス

EC2 > ロードバランサー > ロードバランサー名 > DNS名

プッシュコマンドの表示

ECS > Amazon ECR > プライベートレジストリ > リポジトリ > リポジトリ名 > プッシュコマンドを表示

ローカルファイル修正後、コンテナ内でコマンドを実行

ローカルファイル修正 > ECRにPush > ECS > クラスター名 > サービス名 > 新しいデプロイの強制 > サービスを更新 > クラスター名 > タスク > 新しいタスクの実行 > コンテナの上書き

おわりに

以下の構成で Rails アプリケーションを本番公開できます。

Route53 → ALB → ECS Fargate → RDS

ECS → S3
ECS → SES

JavaScript Primer - 迷わないための入門書 #jsprimerを読んだ感想

JavaScript Primer - 迷わないための入門書 #jsprimerを読んだ感想をまとめます。

良かったところ

  • ウェブ版ではインタラクティブにサンプルコードをその場で実行できる機能があり、リアルタイムで動きを確認しながら学べるところ。
  • ECMAScript 2015(ES2015)以降の最新の書き方をベースにしつつ、旧記法の書き方も併記されているため、旧コードの理解や過去との違いがひと目で把握できるところ。
  • 基本文法から応用的なユースケース、小さなアプリ開発の実例や関連ツールの紹介まで、幅広く体系的にカバーされており、網羅的に学べるところ。


学んだこと

JavaScriptとは
  • JavaScriptという言語はECMAScriptという仕様によって動作が決められている。
  • ECMAScriptという仕様では、どの実行環境でも共通な動作のみが定義されているため、基本的にどの実行環境でも同じ動作をする。


コメント
  • // 以降から行末までが一行コメント
  • /**/で囲まれた範囲が複数行コメント


変数と宣言
  • まずconstで定義できないかを検討し、できない場合はletを使うことを推奨している。


演算子
  • NaNは"Not-a-Number"の略称で、数値ではないがNumber型の値を表現している。
  • 暗黙的な型変換が行われる等価演算子(==)は意図しない挙動となることがあるため、使うべきではない。代わりに、厳密等価演算子(===)を使い、異なる型を比較したい場合は明示的に型を合わせるべき。


暗黙的な型変換
  • 数値から文字列へ明示的に変換する場合は、Stringコンストラクタ関数を使う。
  • 文字列から数値へ明示的に変換する場合はNumberコンストラクタ関数を使う。


関数と宣言
  • 可変長引数が必要な場合はarguments変数よりも、Rest parametersでの実装を推奨している。
  • 関数はただのオブジェクトとは異なり、関数名に()をつけることで、関数としてまとめた処理を呼び出すことができる。
  • 一方で、()をつけて呼び出されなければ、関数をオブジェクトとして参照できる。
  • Arrow Function は無名関数を定義する構文。
  • Arrow Functionで問題ない場合はArrow Functionで書き、そうでない場合はfunctionキーワードを使うことを推奨している。


文と式
  • ブロックで終わる文は例外的にセミコロンをつけなくてよい。


ループと反復処理
  • 配列の内容に対して反復処理を行う場合は、for文やforEachメソッド、後述するfor...of文を使う。


オブジェクト
  • プロパティが存在するかが重要な場合は、基本的にはin演算子またはObject.hasOwn静的メソッドを使う。


プロトタイプオブジェクト
  • ほとんどすべてのオブジェクトはObject.prototypeプロパティに定義されたprototypeオブジェクトを継承している。
  • prototypeオブジェクトとは、すべてのオブジェクトの作成時に自動的に追加される特殊なオブジェクト。


配列
  • 配列の先頭や末尾の要素を削除する場合はArrayのshiftメソッドやpopメソッドを使う。
  • 配列の任意のインデックスの要素を削除するにはArrayのspliceメソッドを使う。
  • sliceメソッドとconcatメソッドは引数なしで呼び出すと、その配列のコピーを返す。


関数とスコープ
  • 内側から外側のスコープへと順番に変数が定義されているか探す仕組みのことをスコープチェーンと呼ぶ。
  • varによる変数宣言は、宣言部分が暗黙的にもっとも近い関数またはグローバルスコープの先頭に巻き上げられ、代入部分はそのままの位置に残る。
  • functionキーワードを使った関数宣言もvarと同様に、もっとも近い関数またはグローバルスコープの先頭に巻き上げられる。


クラス
  • クラスとは動作や状態を定義した構造。
  • コンストラクタとは、そのクラスからインスタンスを作成する際にインスタンスに関する状態の初期化を行うメソッド。
  • クラスでは、プロパティの参照(getter)、プロパティへの代入(setter)。


非同期処理
  • 同期的にブロックする処理があると、ブラウザではスクロールや表示の更新もブロックされる。
  • 非同期処理はコードを順番に処理していくが、ひとつの非同期処理が終わるのを待たずに次の処理を評価する。 つまり、非同期処理では同時に実行している処理が複数ある。
  • 並行処理とは、処理を一定の単位ごとに分けて処理を切り替えながら実行すること。
  • 糖衣構文(シンタックスシュガー)とは、同じ意味の処理を元の構文よりシンプルに書ける別の書き方のこと。
  • await式では非同期処理を実行して完了するまで、次の行(次の文)を実行しない。 そのためawait式を使うことで非同期処理が同期処理のように上から下へと順番に実行するような処理順で書ける。


JSON


Ajax通信
  • エントリーポイントとは、アプリケーションの中で一番最初に呼び出される部分のこと。Webアプリケーションにおいては、常にHTMLドキュメントがエントリーポイントとなる。
  • DOM(Document Object Model とは、HTMLドキュメントのコンテンツと構造をJavaScriptから操作できるオブジェクト。
  • Fetch APIはHTTP通信を行ってリソースを取得するためのAPI。Fetch APIを使うことで、ページ全体を再読み込みすることなく指定したURLからデータを取得できる。


難しかったこと

  • 初学者の立場からは、特にアロー関数と従来の関数の違い、非同期処理(Promise や async/await)の流れなどは、一読しただけでは理解しきれず、内容がなかなかすっと頭に入ってきませんでした。解説は丁寧で情報量は豊富ですが、それがかえって「覚えるべき要素が多い」と感じることにもつながり、一部の内容は後から辞書的に何度も確認しながら理解を深めていきたいと思いました。

Everyday Rails - RSpecによるRailsテスト入門を読んだ感想

Everyday Rails - RSpecによるRailsテスト入門を読んだ感想をまとめます。

良かったところ

  • Railsのテストの書き方について網羅的に学べること。
  • 他のRailsの参考書では、テストの書き方が全体の一部として簡単に触れられていることが多いが、本書はテストに特化して一冊にまとめられており、より深く体系的に学ぶことができること。


学んだこと

1. イントロダクション
  • 著者のRailsテストへの基本的な信条は以下。
    • テストは信頼できるものであること
    • テストは簡単に書けること
    • テストは簡単に理解できること


3. モデルスペック
  • 昔はshould構文を使っていたが、現在はexpect構⽂を使用している。
  • テストコードが意図した通りに動いていることを確認するために、⼀時的にバリデーションをコメントアウトしたり、テストを書き換えたりして、テストがちゃんと失敗することを確認する。
  • テストの場合は DRY であることよりも読みやすいことの⽅が重要。
  • 期待する結果は能動形で明⽰的に記述する。 example の結果がどうなるかを動詞を使って説明する。
  • 起きてほしいことと、起きてほしくないことをテストする。
  • 境界値テストをする。


4. 意味のあるテストデータの作成
  • FactoryBot.buildを使うと新しいテストオブジェクトをメモリ内に保存する。
  • FactoryBot.createを使うとアプリケーションのテスト⽤データベースにオブジェクトを永続化する。
  • ファクトリは、ソフトウェアを検証するために必要なサンプルデータを作成する。
  • しかし、ファクトリを使うとテスト中に予期しないデータが作成されたり、無駄にテストが遅くなったりする原因になる。


5. コントローラスペック
  • コントローラのテストは対象となる機能の単体テストとして最も有効活⽤できるときだけ使うのがよい。


6. システムスペックで UI をテストする
  • システムスペックではたくさんの異なる部品が統合されて、⼀つのアプリケーションになっ ていることをテストします。
  • システムスペックは 受⼊テスト、または統合テスト と呼ばれる。


8. スペックを DRY に保つ
  • letは呼ばれたときに初めてデータを読み込む、遅延読み込みを実現するメソッド。
  • letはbeforeブロックの外部で呼ばれるため、セットアップに必要なテストの構造を減らすこともできる。


9. 速くテストを書き、速いテストを書く
  • モック(mock) は本物のオブジェクトのふりをするオブジェクトで、テストのために使われる。
  • スタブ(stub)はオブジェクトのメソッドをオーバーライドし、事前に決められた値を返す。
  • テスト内でオブジェクトをモック化するためにテストダブルを使う場合、できるかぎり検 証機能付きのテストダブルを使うようにする。
  • タグ機能を使うと、特定のテストだけを実⾏し、それ以外はスキップするようにフラグを⽴てることができる。
  • モックやタグの活⽤はどちらもテストスイートの実⾏時間を減らすためのもの。


10. その他のテスト
  • VCR を使えばテストを⾼速に保ち、 API の呼び出しを必要最⼩限に抑えることができる。


11. テスト駆動開発に向けて
  • テスト駆動開発(TDD)
  • 「外から中へ」のテストをするときは、⾼レベルのテストから始めて、ソフトウェアが意図したとおりに動いていることを確認する。
  • もし、今使っているテストから⼗分なフィードバックが得られない場合は、レベルをもう⼀段下げてみる。


12. 最後のアドバイス
  • TDDを始める時は、⼩さなテストで練習する。
  • テストを使ってコードを改善していく。


難しかったこと

  • RSpecの初学者にとっては、コードの解説が少なく、内容を理解するのが難しく感じられた。
  • さまざまな構文やgemが登場するため、習得に時間がかかりそうで、少し難しく感じた。

現場で使える Ruby on Rails 5速習実践ガイドを読んだ感想

現場で使える Ruby on Rails 5速習実践ガイドを読んだ感想をまとめます。

良かったところ

  • Railsの環境構築方法を学べること。
  • RailsMVCという考え方について学べること。
  • CRUD機能を備えたWebアプリケーションの実装方法を学べること。
  • 自動テストについて学べること。
  • バージョンアップに対しての対応方法を学べること。


学んだこと

Chapter2
  • RailsMVC
    • MVCの主な目的は、アプリケーションの中でもUIの部分とアプリケーション固有のデータや処理の扱いは性質が異なり、両者を混ぜて記述するとコードが複雑化して保守性が悪くなってしまう。そのためそれらをMVCの3つの要素に分けて管理しやすくしよう、というのが目的。
    • モデルは、データとデータに関わるビジネスロジック(アプリケーション特有の処理)をオブジェクトとして実装したもの。
    • ビューは、ブラウザに表示する画面、すなわちHTMLなどのHTTPレスポンスの中身を実際に組み立てる部分。
    • コントローラは、ユーザーが操作するブラウザなどのクライアントからの入力(リクエスト)を受け、適切な出力(レスポンス)を作成するための制御を行う部分。


Chapter3
  • アクション

    • リクエストを処理するコントローラとアクションは、ブラウザのリクエストに含まれるURLとHTTPメソッドによって決定される。
    • コントローラのアクションを設計する際は、入口となるURLとHTTPメソッドを合わせて考える必要がある。
  • コントローラのインスタンス変数

    • ビューからも見ることができる。
    • アクションからビューに受け渡しをしたいデータをインスタンス変数に入れることが、アクションの基本的な役割のひとつとなる。
  • アクションへデータを送る「リクエストパラメータ」

    • Webアプリケーションでは、リクエストにデータを添えることができる。このデータを「リクエストパラメータ」と呼ぶ。
    • リクエストパラメータの送り方は2通りに別れる。
      • POSTで送る。基本的にはHTMLのform要素をsubmitすることで送られる。
      • GETで送る。基本的にはURLの?以降に情報を含めることで送る。


Chapter4
  • マイグレーションの適用

  • モデル検証

    • Railsにおけるモデルの検証は基本的に「レコードをデータベースに登録・更新する前に検証を行い、エラーがあれば登録・更新しないで差し戻す」という仕組み。
    • 検証コードを書く方法には2通りある
      • Railsが用意している検証用のヘルパーを利用する。
      • 自分で任意の検証コードを記述する。


Chapter5
  • テストを書くことのメリット
    • 動作の確認の大部分を自動テストに任せることができ、テスト全体にかかるコスト大幅に削減できる。
    • 十分な自動テストがあれば、新しい変更が既存の機能の動作を損なっているかを自動的にチェックできるので、頻繁にリリースがしやすくなる。
    • テストを書きやすいコードを作ることを意識することで、おのずと、管理しやすい粒度で構成されたコードにしていける。


Chapter6
  • ルーティング
    • ルーティングの基本動作には2通りある
      • ルートはHTTPメソッドとURLからアクションに案内するための定義。
      • URLパターンに名前をつけておいて、その名前をもとにURLを簡単に生成するためのヘルパーメソッドを作り出す。
    • resourcesはCRUDのアクションのルーティングを一括で登録することができる。


Chapter8
  • Ajax

    • Webブラウザ上で非同期処理を行い、ページの再読み込みなしにページを更新するためのJavaScriptのプログラミング手法。
  • Yarn

    • Facebookにより開発されたJavaScriptのパッケージマネージャ。
    • 「パッケージ」とはライブラリの公開単位で、コードなどのファイルをひとまとめにしたもののことを指す。
    • Yarnはnpmより高速に動作し、また、チェックサムを使ってライブラリの内容に予期せぬ変更が加わっていないことを確認してくれるセキュリティが高い特徴がある。
  • Webpacker

    • JavaScriptのビルドツール「Webpack」のラッパーで、RailsアプリケーションでWebpackを使ってJavaScriptを管理することを簡単にしてくれるGem。


Chapter9
  • Gitのpush -fに気を付ける
    • 他の人も触るリモートリポジトリ上のブランチであれば、関係者に声をかけて、問題のないタイミングでrebaseの反映のためのpush -fをする。
    • 通常の変更を積む目的であれば基本的にpush -fはしない。rebaseしてからpushするなどする。


Chapter10
  • バージョンアップにどう取り組むか
    • Railsでアプリケーションを開発するならば、アプリケーションを放棄しない限り、ずっとバージョンアップし続ける必要がある。
    • 開発現場だけでなく、事業計画のレベルでも、RubyRailsのバージョンアップの費用を常に計画に織り込むことが重要。


難しかったこと

  • コードについての解説が少なかったので、書いてあるコードの意味を理解できないところが多かった。今後は、実際にRailsを書き、わからないときにもう一度読んで理解を深めていきたい。

達人に学ぶDB設計を読んだ感想

達人に学ぶDB設計を読んだ感想をまとめます。

良かったところ

・章の最初に学習のポイントがまとめらている

本書では、章の最初に今から学習する章のポイントが箇条書きでまとめられています。

ここを章の初めに読むことでこれから読む本文の内容が理解しやすかったです。

また、読み返す時にもこのページを見ることで章で学んだことをすぐに思い出すことができます。

・勘どころという吹き出し

本文中で説明の結論が勘どころという吹き出しに一言でまとまっています。

この勘どころの結論を確認することで本文中の説明が多少難しくても頭を整理して読むことができました。

・学んだ方式のどれを採用すれば良いかが分かる

DB設計では様々な技術や方式があり、それぞれの技術や方式には利点、欠点があります。

本書では、最初に利点、欠点を説明し、それを踏まえてどの技術や方式を採用していくべきか書かれています。

なので自分が実際にDB設計をする際に迷いが少なく設計できると思います。


学んだこと

・DB設計の知識を一通り学んだ

論理設計から物理設計、正規化、ER図、アンチパターンまで幅広くDB設計の知識を学習しました。

また、正規化の目的やアンチパターンのどこが悪なのかという根本的なところも理解することができました。

・DB設計におけるトレードオフ

例えば正規化についての場合、正規化を行うことの利点として、データの冗長性が排除され、更新時の不整合を防止できます。

一方で欠点としてテーブルの数が増えるため、SQL文で結合を多用することになり、パフォーマンスが悪化します。

このようにDB設計では度々トレードオフの関係が発生します。

本書を通して、DB設計は利点だけに目を向けるのではなく、欠点も頭に入れて設計することの重要性を学びました。


難しかったこと

・第4、5正規形について

第4、5正規形にするためにテーブルを分割する方法があまりイメージできず、難しく感じました。

正規化については、実際に自分でDB設計していく中で本書を参考にしながら慣れていきたいと思います。

スッキリわかるSQL入門を読んだ感想

スッキリわかるSQL入門を読んだ感想をまとめます。

良かったところ

吹き出し会話

本書では、2人の男の子と女の子がSQLを学んでいく設定になっています。

その2人の会話が吹き出し形式で本文中に入っていることで読みやすくなっています。

・各章の練習問題

各章の章末には練習問題があります。

章末の練習問題でその章の理解度を確認することができます。

・テーブルの設計方法を学べる

最後のchapterでは要件に応じた適切なテーブル設計を行う手順と方法を学ぶことができます。

要件定義から物理設計までの流れが詳しく書かれていたので良かったです。


学んだこと

SQLの知識を一通り学んだ

SQLの各種文法やDBMSの機能、テーブルの設計方法まで幅広く学習することができました。

SQLの文法(SELECT文、UPDATE文)については前知識があったのでより理解を進めることができました。

DBMSの機能、テーブルの設計方法については新しく知識をつけることができました。


難しかったこと

・ER図についての内容が難しかった

データベース設計で作成する必要があるER図の内容が難しかったです。

実際に自分でER図を書いてデータベースを設計するのは難しそうだと感じました。

ER図も含め、まだ理解が追いついてないところが多々あるので、本書を参考にアウトプットして知識を身につけていきたいと思います。