Open11

ISUCONインフラを頑張るメモ

まきぞうまきぞう

Golangの実装にて、本番環境にフロント、サーバー、データベースをデプロイする方法を調べる。

まきぞうまきぞう

全てのチームメンバーが、EC2インスタンス上にSSH接続ができる状態にしておく必要がある。

そのために、チームメンバーが作った公開鍵を全てのサーバーのauthorized_keysに記述しておく必要がある。

まきぞうまきぞう

この人は一つのEC2サーバーにてSSHログインし、ブランチで変更があるたびに手動でデプロイスクリプトを実行していくような形式をとっているんだな。

非常に参考になる。

まきぞうまきぞう

サーバー上でgit cloneしてデプロイするのか、docker pullしてきてデプロイするのか、どちらの方が速度が速いのだろうか?

まきぞうまきぞう

docker

メリット

  • アプリケーションを実行するためのパッケージを都度インストールする必要もなく、言語の実行環境もインストールする必要がない。必要なのはデプロイスクリプトファイルを取得するのとdockerをインストールするだけ。
  • アプリケーションを置いておくためのディスク容量を節約できる。
  • imageをpullすれば良いだけなので、自分がデプロイしたいブランチのタグをdocker pushして、サーバー上からpullすればデプロイブランチの切り替えもできる。

git

メリット

  • デプロイのたびにgit pushの他にdocker pushしなくても良い
  • 情報が多い
  • dockerをインストールする際のオーバーヘッドが大きかった場合は、gitのほうが早いケースがある。

一旦dockerとgitで両方デプロイできるようにしてみて、ベンチマーカースコアが高かった方を採用していくようにしよう。

まきぞうまきぞう

サーバーの再起動後にもアプリケーションが動作していることを保証する必要もあることを忘れないようにしよう

まきぞうまきぞう

デプロイ方法の検討(Gitの場合)

mainブランチにコミットがあった際に自動的にデプロイ

GoのAPIをEC2インスタンスに自動デプロイするためのGitHub Actionsワークフローを以下のように設定できます:

  1. GitHub Actionsのワークフロー設定

.github/workflows/deploy.ymlファイルを作成し、以下の内容を記述します:

name: Deploy to EC2

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2

    - name: Set up Go
      uses: actions/setup-go@v2
      with:
        go-version: 1.x

    - name: Build
      run: go build -v ./...

    - name: Deploy to EC2
      env:
        PRIVATE_KEY: ${{ secrets.EC2_SSH_PRIVATE_KEY }}
        HOST: ${{ secrets.EC2_HOST }}
        USER: ${{ secrets.EC2_USER }}
      run: |
        echo "$PRIVATE_KEY" > private_key && chmod 600 private_key
        scp -i private_key -o StrictHostKeyChecking=no ./your-api-binary $USER@$HOST:/path/to/deployment/
        ssh -i private_key -o StrictHostKeyChecking=no $USER@$HOST '
          sudo systemctl stop your-api-service
          sudo cp /path/to/deployment/your-api-binary /path/to/production/
          sudo systemctl start your-api-service
        '
  1. GitHubシークレットの設定

GitHubリポジトリの"Settings" > "Secrets"で以下のシークレットを設定します:

  • EC2_SSH_PRIVATE_KEY: EC2インスタンスにアクセスするためのSSH秘密鍵
  • EC2_HOST: EC2インスタンスのパブリックDNSまたはIPアドレス
  • EC2_USER: EC2インスタンスのSSHユーザー名(通常は"ec2-user"や"ubuntu")
  1. EC2インスタンスの設定
  • EC2インスタンスのセキュリティグループで、GitHubのIPレンジからのSSH接続を許可します[1]。
  • APIを実行するためのシステムサービスを設定します。
  1. デプロイスクリプトの調整

上記のワークフローでは、ビルドしたバイナリをSCPでEC2インスタンスに転送し、SSHコマンドでサービスの再起動を行っています。実際の環境に合わせて、パスやサービス名を調整してください。

このセットアップにより、mainブランチへのプッシュがあるたびに:

  1. コードがチェックアウトされます。
  2. Goの環境がセットアップされます。
  3. アプリケーションがビルドされます。
  4. ビルドされたバイナリがEC2インスタンスに転送されます。
  5. EC2インスタンス上でサービスが再起動され、新しいバージョンが反映されます。

注意点:

  • セキュリティを考慮し、EC2インスタンスへのSSHアクセスは制限し、必要最小限の権限を付与してください。
  • 本番環境では、ダウンタイムを最小限に抑えるためのデプロイ戦略(ブルー/グリーンデプロイメントなど)を検討することをお勧めします。
  • デプロイ前後にテストを実行し、アプリケーションの健全性を確認することも重要です[2][3]。

このアプローチにより、GitHubのmainブランチへの変更が自動的にEC2インスタンスに反映され、継続的デリバリー(CD)のパイプラインが実現できます。

Citations:
[1] https://lightrains.com/blogs/deploy-aws-ec2-using-github-actions/
[2] https://stackoverflow.com/questions/62087265/deploy-code-directly-to-aws-ec2-instance-using-github-actions
[3] https://aws.amazon.com/blogs/devops/integrating-with-github-actions-ci-cd-pipeline-to-deploy-a-web-app-to-amazon-ec2/
[4] https://qiita.com/kaino5454/items/bae4bca47e6f409e072f
[5] https://gist.github.com/raviagheda/c69ae5e884f4490b1af656dbd80c00dd
[6] https://www.youtube.com/watch?v=BDGTIM8fTUc
[7] https://qiita.com/kubota_ndatacom/items/1055df9b1568b86ddb41
[8] https://knowledge.sakura.ad.jp/23478/

メンバーがSSHでEC2インスタンスに接続し、特定のブランチをデプロイ

特定のブランチを指定してEC2インスタンスにSSH接続し、Go製APIのコードを取り込んでデプロイする手動の方法を以下に示します。この方法では、ローカルから開発メンバーが直接デプロイできます。

  1. デプロイスクリプトの作成

ローカルマシンに deploy.sh というスクリプトを作成し、以下の内容を記述します:

#!/bin/bash

# 使用方法: ./deploy.sh <branch_name>

if [ $# -eq 0 ]; then
    echo "使用方法: $0 <branch_name>"
    exit 1
fi

BRANCH=$1
EC2_USER="ec2-user"  # EC2インスタンスのユーザー名
EC2_HOST="your-ec2-instance-ip"  # EC2インスタンスのIPアドレスまたはドメイン名
APP_DIR="/path/to/your/app"  # EC2インスタンス上のアプリケーションディレクトリ
SSH_KEY="path/to/your/ec2-key.pem"  # EC2インスタンスへのSSH鍵のパス

# EC2インスタンスに接続してデプロイを実行
ssh -i $SSH_KEY $EC2_USER@$EC2_HOST << EOF
    cd $APP_DIR
    git fetch origin
    git checkout $BRANCH
    git pull origin $BRANCH
    go build -o api_server
    sudo systemctl restart your-api-service  # システムサービス名を適切に変更してください
    exit
EOF

echo "デプロイが完了しました。ブランチ: $BRANCH"
  1. スクリプトの実行権限を付与
chmod +x deploy.sh
  1. デプロイの実行

特定のブランチをデプロイするには、以下のコマンドを実行します:

./deploy.sh feature-branch-name

このスクリプトは以下の処理を行います:

  1. 指定されたブランチ名を引数として受け取ります。
  2. EC2インスタンスにSSH接続します。
  3. アプリケーションディレクトリに移動します。
  4. 指定されたブランチの最新コードを取得します。
  5. Goアプリケーションをビルドします。
  6. システムサービスを再起動してアプリケーションを更新します。

注意点:

  • EC2インスタンスのセキュリティグループで、開発者のIPからのSSH接続を許可する必要があります。
  • EC2_USER, EC2_HOST, APP_DIR, SSH_KEYの値は、実際の環境に合わせて変更してください。
  • システムサービス名(your-api-service)は、実際のサービス名に変更してください。
  • EC2インスタンス上にGitリポジトリがクローンされていることを前提としています。まだクローンされていない場合は、最初のデプロイ時にクローンする手順を追加してください。
  • このスクリプトは基本的な例です。実際の環境では、エラーハンドリングやログ記録などを追加することをお勧めします。

このアプローチにより、開発メンバーは特定のブランチ名を指定するだけで、簡単にEC2インスタンスにコードをデプロイできます。ただし、本番環境では適切なセキュリティ対策と承認プロセスを設けることが重要です。

Citations:
[1] https://www.oracle.com/jp/developer/oci-functions-in-go-and-using-the-oci-go-sdk-for-accessing-oci-services-from-go/
[2] https://zenn.dev/jinwatanabe/articles/6254192b73e7772bcd17
[3] https://cloud.google.com/appengine/docs/legacy/standard/go111/testing-and-deploying-your-app?hl=ja
[4] https://qiita.com/kaino5454/items/bae4bca47e6f409e072f
[5] https://devcenter.heroku.com/ja/articles/getting-started-with-go?singlepage=true
[6] https://learn.microsoft.com/ja-jp/azure/developer/go/azure-sdk-qs-vm
[7] https://qiita.com/Dragon-taro/items/09a67b93093770348ce5
[8] https://qiita.com/konbraphat51/items/0ff0555dcdc538123d61

補足

再起動試験に備えるため、systemctlのserviceに登録しておくことを忘れずに!

複数台でのサーバー運用となっている場合、こちらを参照して、それぞれのEC2インスタンスにssh接続してデプロイするようにすると良い
https://qiita.com/momotaro98/items/694000dfb736d0316441

まきぞうまきぞう

デプロイ方法の検討(Dockerの場合)

開発ユーザーが特定のブランチを指定してEC2にデプロイするケース

はい、その方法は非常に効果的で一般的なアプローチです。Docker イメージを使用することで、環境の一貫性が保たれ、デプロイプロセスがよりクリーンになります。以下に、ローカルでイメージをビルドし、Docker Hub にプッシュし、EC2 インスタンスでデプロイするための包括的なシェルスクリプトを示します。

以下の deploy.sh スクリプトを作成してください:

#!/bin/bash

# 使用方法: ./deploy.sh <branch_name>

if [ $# -eq 0 ]; then
    echo "使用方法: $0 <branch_name>"
    exit 1
fi

BRANCH=$1
DOCKER_USERNAME="your-dockerhub-username"
IMAGE_NAME="your-api-image"
EC2_USER="ec2-user"
EC2_HOST="your-ec2-instance-ip"
SSH_KEY="path/to/your/ec2-key.pem"

# ブランチをチェックアウト
git checkout $BRANCH
git pull origin $BRANCH

# Dockerイメージをビルド
docker build -t $DOCKER_USERNAME/$IMAGE_NAME:$BRANCH .

# Docker Hubにイメージをプッシュ
docker push $DOCKER_USERNAME/$IMAGE_NAME:$BRANCH

# EC2インスタンスに接続してデプロイを実行
ssh -i $SSH_KEY $EC2_USER@$EC2_HOST << EOF
    # 古いコンテナを停止して削除
    docker stop api-container || true
    docker rm api-container || true

    # 新しいイメージをプル
    docker pull $DOCKER_USERNAME/$IMAGE_NAME:$BRANCH

    # 新しいコンテナを起動
    docker run -d --name api-container -p 8080:8080 $DOCKER_USERNAME/$IMAGE_NAME:$BRANCH

    # 不要なイメージを削除(オプション)
    docker image prune -af

    exit
EOF

echo "デプロイが完了しました。ブランチ: $BRANCH"

このスクリプトを使用する前に、以下の点を確認してください:

  1. Docker がローカルマシンとEC2インスタンスの両方にインストールされていること。

  2. ローカルマシンで Docker Hub にログインしていること:

    docker login
    
  3. EC2インスタンスでも Docker Hub にログインしていること(初回のみ必要):

    ssh -i $SSH_KEY $EC2_USER@$EC2_HOST
    docker login
    
  4. スクリプト内の以下の変数を適切に設定すること:

    • DOCKER_USERNAME
    • IMAGE_NAME
    • EC2_USER
    • EC2_HOST
    • SSH_KEY
  5. アプリケーションのルートディレクトリに適切な Dockerfile が存在すること。

  6. スクリプトに実行権限を付与すること:

    chmod +x deploy.sh
    

使用方法:

./deploy.sh feature-branch-name

このスクリプトは以下の処理を行います:

  1. 指定されたブランチをチェックアウトし、最新の変更を取得します。
  2. Dockerイメージをビルドします。
  3. ビルドしたイメージをDocker Hubにプッシュします。
  4. EC2インスタンスにSSH接続します。
  5. EC2インスタンス上で古いコンテナを停止・削除します。
  6. 新しいイメージをプルします。
  7. 新しいコンテナを起動します。

このアプローチの利点:

  • 環境の一貫性が保たれます。
  • ロールバックが容易です(以前のタグのイメージを使用するだけ)。
  • EC2インスタンス上でのビルドプロセスが不要になります。

注意点:

  • Docker Hubの代わりにプライベートレジストリを使用する場合は、認証情報の管理に注意してください。
  • 本番環境では、さらなるセキュリティ対策(例:イメージのスキャン、承認プロセスなど)を検討してください。
  • 必要に応じて、環境変数やシークレットの管理方法を追加してください。

このスクリプトを使用することで、開発者は簡単にDockerイメージをビルドし、Docker Hubを経由してEC2インスタンスにデプロイすることができます。

まきぞうまきぞう

システム構成の検討

以前はサーバーの台数が1台だけだったと言う事例もあるが、3台だと仮定する。
その時は、オーソドックスな3層構造にするという戦略で行こうと思う。

  1. Webサーバー
  2. APIサーバー
  3. DB

https://qiita.com/Sinclair/items/0d284caee5862ea681dd

余力があれば、ベンチマーカーを使ってワークロードを与え、リソースの使用状況に応じて負荷分散したりしたいな

まきぞうまきぞう

現時点での総まとめ

必須要件【これができないと失格】

デプロイ方式は開発メンバーが自由なブランチを指定して本番環境にデプロイできるようにしておく

実行環境はDocker Containerを走らせる方式でいく(Webサーバー, APIサーバー, DB)

Dockerでデプロイする方法詳細はこちら: https://zenn.dev/link/comments/228d8b1e7d2191

できたら要件【スコアをアップさせるために必要】

  • 現在どのブランチがデプロイされているのかをSlack通知させる
  • Prometheus + Grafanaでモニタリング環境を整備
  • 最終提出前に開発用に使っていた、実行に必要ないツールを削除(Prometheus, Grafanaなど)