はじめ
こんにちは、アプリケーションエンジニアとして働いてます。キム ソンジュです。 今回の記事では自分が参加したPJで利用した、インフラ構成から、CI/CD環境を利用して簡単にアプリケーション開発ができる方法について紹介しようと思います。
システム投入・設計背景
既存のレガシーシステムには、次の問題がありました。
- デプロイの手順が複雑で時間かかり、面倒な作業が多い
- 環境ごとにミドルウェアのバージョンが異なる
この問題を解決し、かつ新しい技術にチャレンジするために、チーム内で次の内容で進めるようチームで決めました。
- Dockerを活用して環境ごとの差をなくす
- GitHubでソースコードを管理するので、CI/CDにはGitHub Actionsを採用
- Dockerを利用することによる、ECRとECSを活用
入る前に
本記事で話したい内容は「このような方法で、こんなに簡単にアプリの開発からデプロイまでできる」といった紹介をするのが目的です。 案内してるAWSの各技術の細かい使い方、テストで使ってるGo言語の深い話については紹介しませんのでその点をご了承ください。
登場人物
- AWS IAM
- AWS ECR
- AWS ECS
- AWS ALB(※1)
- AWS Parameter Store(※2)
- Github Actions
- Docker
- Go ( Go以外のアプリケーションも利用可能です)
※1. ALBはFargateを利用する時の必須技術になります。
※2. Parameter Storeは、Dockerを起動させる時に環境変数を利用して敏感な情報をアプリで利用するため使います。
※その他の登場人物については、上記で決めたことを元にして登場しました。
作業結果の予想図
上記の登場人物を利用する場合、下記のような構成が出ます。
作業の流れ
- Goを利用したアプリ作成
- Dockerを利用したContainer環境構築
- Local環境起動確認・テスト
- AWS設定
- Github Actions設定
- 実装テスト
こちらの流れで作業を進めて行く予定です。 それでは始めましょう!
1. Goを利用したアプリ作成
- go-json-resetのModuleを利用します。
- この記事では上記の例文をそのまま利用する予定です。
- go moduleを利用します。
- 参考コードはこちらです。
2. Dockerを利用したContainer環境構築
- goが設定されてるAlphine Linuxを利用します。
- TimeZone設定がDefault UTCなので調整します(ログ書くときに時間がずれることを予防します)
- go moduleの設定と、Install作業を行います。
- buildした後、実行させます。
dockerfile
## get alphine + go image ver.1.14 FROM golang:1.14.2-alpine3.11 ## pakcage update & install RUN apk add --update curl git pkgconfig curl-dev ## modified timezone RUN apk add tzdata ENV TZ=Asia/Tokyo ## setup env variables for go module ENV GO111MODULE "on" ## source code copy from host disk WORKDIR /go COPY . /go/src ## module install ## If You didn't set up go.mod in your local workspace, You can not use command go install WORKDIR /go/src RUN go install ## go build RUN go build ./main.go ## container listen port EXPOSE 8080 ## command ## start CMD ["./main"]
ここまで来たら下記のようなディレクトリ構成になります。
. ├── Dockerfile ├── go.mod // go module 設定ファイルになります。 ├── go.sum // なくても構いません。 └── main.go
3. Local環境起動確認・テスト
- docker build
- postman, curl などを利用して、テスト
docker build -t lifull_test . docker run -p 8080:8080 qiita_test:latest
自分はPostmanを利用して見ました。 アクセスログが出力されているか見てみましょう?! よし、ここまで来たら準備は完了です。
4. AWS設定
AWSの環境を設定しましょう、ALB,SGの細かい設定についてはこの記事では案内しません、必要最低限の設定で作成します
IAM 設定
- Github Actionsが利用するDeploy用ユーザーを作成
- 必要権限は ECR,ECSに関連する権限を付与します。
- 作成時 AccesToken、Keyを保存しましょう。
- FargateのTaskが使うRoleを設定します。
- ecsTaskExecutionRole この名がDefaultです。
- 権限は、ECR,ECSの権限に更に、CloudWatch,Parameter Storeへの利用権限が必要です。
- Github Actionsが利用するDeploy用ユーザーを作成
Parameter Store 設定
アプリで利用する各種環境変数周りがあります(Laravelの場合.env周りに設定する環境変数です)こちらをGo+Dockerで利用する場合、環境変数をOSで扱うのが一般的で、こちらを実現するためにAWSでは SystemManager > Parameter Storeに設定します。
key-valueで設定します。暗号化された文字列として保存するのがより安全ですね!
- Security Group 作成
- 利用するポートを許可します。 今回の場合は8080をOpenしましょう!
- ALB 作成
- Public Subetを設定します。
- Public DNSが使える状態にします。もしくはHTTPS設定を利用して使える場合DNS設定を行います。(ACMなどが登場しますね)
- ECR 作成
- Repositoryを作成します。(Docker-hubのPrivate版的な感じで、AWSでDockerを利用する時よく使います。)
- ECS 設定
- ECSの構成について細かい説明は行いません。下記の設定は注意してください。
- task定義が必要です。
- まだRepositoryにアップしたイメージがないので適当に書いておいても構いません。
- Containerに渡す環境変数の設定が必要です。上記のParameter Storeで設定したパラメーター名を環境変数:パラメーター名構成で作成します。
- Container側に必要はPortをOpenする必要もあります。ここでは8080ですね。
- clusterを作成します。
- serviceを作成します。
5. Github Actions設定
一覧の設定が終わったら、Github Actionsを設定しましょう
Github Actionsについては気になる方は以前自分がQiitaに投稿した記事がありますので、参考にしてください
Trigger
- push
- Target Branch
- master
- doing
- docker build
- task-definition.json を利用したDeploy作業
.github/workflow/main.yml
### main.yml name: test workflow for qiita on: push: # event trigger on push branches: master jobs: build: # job id name: sjkim action # job name runs-on: ubuntu-latest # virtual os steps: - name: set up go 1.14 uses: actions/setup-go@v1 with: go-version: 1.14 - name: Checkout branch go module directory uses: actions/checkout@v2 - name: package install uses: actions/cache@v2 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - 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: ap-northeast-1 - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v1 - name: Build, tag, and push image to Amazon ECR id: build-image env: ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} ECR_REPOSITORY: qiita-test IMAGE_TAG: ${{ github.sha }} run: | # Build a docker container and # push it to ECR so that it can # be deployed to ECS. docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" - name: Fill in the new image ID in the Amazon ECS task definition id: task-def uses: aws-actions/amazon-ecs-render-task-definition@v1 with: task-definition: task-definition.json container-name: qiita-container image: ${{ steps.build-image.outputs.image }} - name: Deploy Amazon ECS task definition uses: aws-actions/amazon-ecs-deploy-task-definition@v1 with: task-definition: ${{ steps.task-def.outputs.task-definition }} service: qiita-service cluster: qiita-test-cluster wait-for-service-stability: true
Github Actions marketplaceにあるECS Deployを元に作成してます。
Github Secretsについて
Github Actions上で利用する秘密情報や、アクセストークンなどを利用する時に使います。
- {{ secrets.AWS_SECRET_ACCESS_KEY }} などで利用可能です。
- setting > secrets > new secrets
task-definition.json
{ "requiresCompatibilities": [ "FARGATE" ], "inferenceAccelerators": [], "containerDefinitions": [ { "name": "qiita-container", "image": "***/qiita-test", "resourceRequirements": null, "essential": true, "portMappings": [ { "hostPort": 8080, "containerPort": "8080", "protocol": "tcp" } ], "secrets": [ { "name": "ENV", "valueFrom": "dev" } ], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/qiita-test", "awslogs-region": "ap-northeast-1", "awslogs-stream-prefix": "qiita" } } } ], "volumes": [], "networkMode": "awsvpc", "memory": "512", "cpu": "256", "executionRoleArn": "****/ecsTaskExecutionRole", "family": "qiita-task", "taskRoleArn": "", "placementConstraints": [] }
taskは上記AWSの作業手順のなか、Taskを作成したら、Task詳細タブの中にJsonて書かれてる部分があります。 その部分をコピしてLocalのPJ内に配置したらOKです。
- Cloud Watchのロググループは存在してない場合、自動的に作成してくれます。(Deployユーザーに権限が無い場合Deploy中にNGになりますので注意)
- secretsの部分でContainerにわたす環境変数を設定します。nameは渡す環境変数名 valueFromはParameter Storeに指定した名前を入れます。
- container name, task name, cluster name, service name など、先に設定しておかない、又はタイポで間違った内容を書くと、Github Actionsの動作中に落ちます。
6. テスト
もうほぼ準備は完了です。 ここまでのPJの構成が下記になります。
├── .github │ └── workflows │ └── main.yml ├── Dockerfile ├── go.mod ├── go.sum ├── main.go └── task-definition.json
実装をしてみましょう!
- GithubのMasterブランチへPushしたら、Github Actionsが起動されます。
- その流れによって、ECSにデプロイ作業が開始されます。
- 作業が終わったら、AWS管理ページを確認し、EC2>ターゲットグループチェックします。
- Public DNSや、利用してるDNSを利用してAPIが叩けるのか確認します!
まとめ
今回このような構成でアプリケーションの開発を行ったのは初の試みです。 AWSの知識が浅かったのでかなり苦労した記憶が残ってますが、かなりいい構成かな…と思ってるのでこちらをベースにPJをどんどん拡張していきたいなと思います。 Firelensなどを利用したLog設定や、Github Actions、AWS SNSを利用したアラーム設定、Containerのリソースモニタリングなど、どんどん入れてみたいなと思ってます。
おそらくこの記事の情報だけではそんなに簡単にアプリを作るのは難しいかもしれません。 でもこの記事の内容を参考にしてもらい、皆さんのPJや、悩みへ少しでもHINTになったら嬉しいです。
本日案内したコードはここを参考してください。