個人開発でDocker, Github Action導入した経験

## :wave: はじめに
外国人開発者のキム·ヒスと申します。現在日本で開発者として働いており、最近は様々な個人プロジェクトに取り組んでいます。その中で、DockerとGithub Actionを導入した経験について振り返り、共有したいと思い投稿しました。

DockerやGithub Actionに不慣れな方のために、以下のURLが参考になるかと思います。

https://docs.docker.com/guides/get-started/

https://docs.github.com/ja/actions

## :eyes: 簡単なプロジェクトを紹介
このプロジェクトは、簡単に言うと個人用のブログでした。
使用技術：Next.js、Nest.js、AWS EC2、AWS Route53、styled-components、Mongo DB、Pm2、Nginx

## :eyes: プロジェクトのリリース
このウェブサイトはそれほど複雑ではなかったため、企画からデザイン、実装に至るまで1ヶ月で完了しました。作品を作った喜びから、早めにリリースすることにしました。

この時、Dockerを使用するかどうかで迷いましたが、Dockerの導入は手間がかかるため、最終的にはPm2を選択しました。GithubからのコードをEC2に移し、ビルドした後、Pm2とNginxを利用してリリースしました。

![structure.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569778/68fd306a-99e3-95bf-5840-b3feeaab22b5.png)

## :scream: 問題発生
約2週間後、フロントエンドのコードを変更し、Gitからコードを受け取ってビルド中に、突然EC2が停止しました。原因はメモリ容量が小さかったためです。使っていたEC2はプリティアだったので、より大きなメモリ容量を選択することができませんでした。そのため、EC2を再起動し、ビルド時にメモリSwapを使って一時的に容量を増やす方法で解決しました。

そして数週間後、もう一度フロントエンドのコードに変更があり、EC2上でビルドしました。再び停止する事態が発生しました。前回と同様、EC2を再起動して再接続することで復旧できましたが、Pm2とNginxが問題を起こしました。原因を1週間探しましたが、見つけることができませんでした。結局、新しいEC2を作成して移行することにしました。環境構築を最初から行うと、本当に時間がかかりました。😔

「問題発生、夜通しで原因探し、解決」このサイクルが繰り返され、退勤後の休憩時間が徐々に減少していく中で、何か大きな問題があることに気づきました。

## :thinking: 工夫したこと
休息時間を確保するために早急に問題を解決したいという気持ちが強かったです。そのため、現状について原因を分析しました。その結果、問題は二つあると考えました。

1. メモリをSwapしてビルドする必要があるが、手動で行わなければならないため忘れがち
2. サーバーを移行する必要がある場合、その移行作業が非常に困難

これらを解決するためには、自動化されたCI/CDパイプラインの実装とDockerの導入が必要だと思いました。DockerはNest.jsでの修正が少ないため、すぐに導入する必要はないと判断しましたが、Next.js、Nginx、MongoDBにはDockerを導入することにしました。

Github Actionは以下のように流れを構成しました。

1. Next.jsプロジェクトをDockerイメージとして構築
2. Docker Hubにプッシュ
3. EC2サーバーでDocker Hubの認証を完了させ、プッシュされたイメージを受け取る
4. シェルスクリプトでDocker Composeを実行

```yml
name: deploy

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    env : 
      VERSION : 1.1
      AWS_REGION : ap-northeast-1
      EC2_IP_ADDRESS : ${{ secrets.AWS_EC2_IP }}

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1

      - name: Login to Docker Hub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_TOKEN }}

      - name: Generate Environment Variables File for Production
        run: |
          echo "NEXT_PUBLIC_EMAIL_ID=$NEXT_PUBLIC_EMAIL_ID" >> .env
          echo "NEXT_PUBLIC_EMAIL_PUBLIC_KEY=$NEXT_PUBLIC_EMAIL_PUBLIC_KEY" >> .env
          echo "NEXT_PUBLIC_EMAIL_TEMPLATE_ID=$NEXT_PUBLIC_EMAIL_TEMPLATE_ID" >> .env
          echo "NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL" >> .env
        env:
          NEXT_PUBLIC_EMAIL_ID: ${{ secrets.NEXT_PUBLIC_EMAIL_ID }}
          NEXT_PUBLIC_EMAIL_PUBLIC_KEY: ${{ secrets.NEXT_PUBLIC_EMAIL_PUBLIC_KEY }}
          NEXT_PUBLIC_EMAIL_TEMPLATE_ID: ${{ secrets.NEXT_PUBLIC_EMAIL_TEMPLATE_ID }}
          NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }} 

      - name: Build and push production
        uses: docker/build-push-action@v2
        with:
          context: .
          file: ${{ secrets.DOCKERFILE_PATH }}
          push: true
          tags: ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_APP_NAME }}:latest,${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_APP_NAME }}:${{ env.VERSION }}
          platforms: ${{ secrets.DOCKER_PLATFORMS }}

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

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

      - name: Deploy on EC2 instance
        uses: appleboy/ssh-action@v0.1.6

        env :
          DOCKER_REGISTRY: ${{ secrets.DOCKER_USERNAME }}
          DOCKER_IMAGE_TAG: latest
          DOCKER_APP_NAME: ${{ secrets.DOCKER_APP_NAME }}

        with:
          host: ${{ env.EC2_IP_ADDRESS }}
          username: ubuntu
          key: ${{ secrets.EC2_SSH_PRIVATE_KEY }}
          script: |
            cd ${{ secrets.DEPLOY_PATH }}
            sudo ./deploy.sh
```

Github Actionは初めての経験でしたが、ドキュメントをじっくりと勉強してみると、すぐに慣れることができました。

その結果、EC2内でビルドする必要がなくなり、メモリをSwapする必要もなくなりました。Docker Hubからイメージを受け取って使用するため、サーバーを移行しても大きな問題がなくなりました。今はNest.jsでもDockerを使用する予定です。

![Frame 34 1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569778/2b614848-8a13-d1fa-d17d-b93129d4ad98.png)

## :writing_hand: 感じたこと
開発時にいつも思うのは、「これくらいは問題ないだろう」と考えると、いつも問題が発生するということです。今回の経験から、問題が発生しそうな箇所は絶対に見逃してはいけないと学びました。また、CI/CDの重要性についても深く理解することができました。これからもCI/CDの構築について詳しく知るエンジニアとして成長したいと思います。読んでいただきありがとうございます。:thumbsup: