ELBとECSとCodedeployを組み合わせて実践的なECSサービスの更新をやってみた

本日は、ELBに紐づいたECSを作成します。
ECSの更新はCodedeployを使用します。

分かりにくいと思うので図解にするとこんな感じです。
・構成図
![スクリーンショット 2025-01-24 005524.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/c6422464-585e-925d-26c4-a607b79d26d7.png)

・CodedeployのECSサービス更新の流れ
CodedeployはECSサービス作成時に設定することで、サービス更新時にはCodedeployでサービスを更新できるようになります。

![スクリーンショット 2025-01-24 010436.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/e757447b-dd4e-cc15-4e96-27b5c19d9fc8.png)

![スクリーンショット 2025-01-24 010721.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/941fba6c-0a71-eb32-858b-503b2289587c.png)

![スクリーンショット 2025-01-24 010954.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/0fc93492-dcac-138d-26c0-1ac337b31329.png)

作業は以下の順番で行います。
今回ECSで作成するアプリですが、pythonのFlaskを使用してWebブラウザに「Hello world」と表示させるwebアプリを作ります。

目次
1. Dockerfileとpythonアプリケーションファイルの作成・ローカルで実行
2. Dockerfileとアプリケーションファイルを ECR にプッシュ
3. ECR から ECS のためのタスク定義の作成
4. ECSがECRにアクセスできるようエンドポイント作成
5. セキュリティグループ作成
6. ELB・ターゲットグループ作成
7. ECS Cluster、ECSサービスの作成
8. Codepipeline でサービスの更新およびテスト



## 1. Dockerfileとpythonアプリケーションファイルの作成・ローカルで実行

pythonアプリケーションファイルと Dockerfile はこんな感じで作りました。

・app.py
```
from flask import Flask
from flask import g
from flask import render_template
from flask import request
from flask import Response

app = Flask(__name__)
@app.route('/')
def hello_world():
  return "hello world"

def main():
  app.debug = True
  app.run(host="0.0.0.0", port=80)

if __name__=='__main__':
  main()
```

特になんてことないFlaskのコードですが、以下のコードは忘れないよう気を付けてください。
app.run(host="0.0.0.0", port=80)

上記のコードですが、指定をしないとデフォルトでは以下のようになってしまいます。
ホストIP：127.0.0.1
ポート：5000

ポートは設定すればよいですが、IPが「127.0.0.1」だと外部からアクセスができなくなります。
「127.0.0.1」だと後でコンテナをローカルで実行した際にテストとしてブラウザに「http://localhost 」でwebサイトが表示されるかテストしますが、この時に表示されません。
ホストを"0.0.0.0"に設定すると外部から接続ができるようになり、コンテナを実行したとき「http://localhost 」でサイトを表示できます。

<b>・Dockerfile<b/>

```
FROM ubuntu:22.04
RUN apt update
RUN apt -y install python3-pip
RUN pip3 install flask
COPY app.py /usr/src/app/
CMD python3 /usr/src/app/app.py
```
コマンドを1行ずつ説明するとこんな感じです。
・ベースイメージはubuntu を使用する。
・OS のアップデートをする。
・python3-pip をインストール。
・flask をインストール。
・同階層の app.py を /usr/src/app/ にコピー
・コンテナ実行時に /usr/src/app/app.py を実行する

Linuxを打てるターミナルやコマンドラインを開いて、Dockerfileがある階層に移動します。
そして以下を実行していきます。

```
$ docker image build .
writing image sha256:ccdebb6115dc5f0494ecd・・・(ランダムの文字列)

$ docker container run -p 80:80 ccdebb6115dc5f0494ecd・・・(ランダムの文字列)

```

Microsoft Edge や Google Chrome などで URLに「http://localhost 」と入力して「hello world」が表示されればOKです。


![スクリーンショット 2025-01-19 114119.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/842e0ffb-8f28-0d42-a8ba-1875ff019272.png)



## 2. Dockerfileとアプリケーションファイルを ECR にプッシュ

まずは AWS コンソールの ECR のコンソールに移動します。
「プライベートリポジトリを作成する」というボタンがあるので、クリックします。
以下の画面が表示されますので、リポジトリ名を入力して、他はデフォルトで作成します。

![スクリーンショット 2025-01-19 111716.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/293b9ed5-0eb6-d430-443d-ef52de11292a.png)

レポジトリが作成されました。

![スクリーンショット 2025-01-19 111807.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/b468c32a-7668-9398-61b6-06cc212e9858.png)

「プッシュコマンドを表示」をクリックするとコマンドが４つ表示されます。
表示された４つのコマンドをローカル（あなたのPC）の Dockerfile があるディレクトリで実行します。
私は VS コードのターミナルで実行するので、「macOS/Linux」用のコマンドを一つずつ実行していきます。コマンドの内容は書いてある通りです。

![スクリーンショット 2025-01-19 111827.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/48519b7a-ce84-72aa-bc0f-2e3af0ebfa22.png)

４つ目のコマンドで latest(最新バージョン)のイメージが作成されました。

![スクリーンショット 2025-01-19 115826.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/1f3264d0-9934-f511-cd3c-3061399eacaa.png)


## 3. ECR から ECS のためのタスク定義の作成

ECR のレポジトリにイメージをプッシュできたら、そのイメージをもとにタスク定義を作成していきます。
このタスク定義から ECS サービスを作成することができます。

タスク定義は任意で問題ありません。CPU、メモリは「Hello-world」を表示させるだけなので一番小さいものにします。

![スクリーンショット 2025-01-22 000837.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/cc57a461-d5c7-30c0-a18c-635e4784e798.png)

名前は任意のものに設定します。今回は Python の Flask を使用しているので、「flask-web」とします。

イメージURIは、先ほど作成した ECR に書かれているので、コピーして貼り付けます。
ポートは任意ですが、今回は ELB から 80 番で接続して、内部の Flask も 80 番なので、両方とも80番で設定します。あとはデフォルトで問題ありません

![スクリーンショット 2025-01-22 001154.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/f0005a40-7500-2040-e36b-bd4c6dc9c25e.png)

CloudwatchLogsを取るように定義を設定します。
これで作成ボタンを押して定義の作成が完了です。


![スクリーンショット 2025-01-22 001438.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/1df92273-8409-06eb-19d5-be951eb2a5e5.png)





## 5. ECSがECRにアクセスできるようエンドポイント作成

この記事の冒頭の構成図から分かる通り、ECS はプライベートサブネットに作成します。しかし、ECR や Cloudwatch、S3 は VPC の外部にあり、プライベートサブネットから直接接続はできません。

プライベートサブネットから VPC 外のサービスにアクセスできるようにするには以下の選択肢があります。

・パブリックサブネットにECSを作成する。（セキュリティ上非推奨）
・Natgatewayを使用する。
・AWS のエンドポイントを作成する。

AWS サービスなら AWS エンドポイントを作成するのが最もセキュリティ上お勧めです。
それでは作成していきます。

エンドポイントを作成する前にエンドポイント用のセキュリティグループを作成してください。
セキュリティグループは以下の設定にしてください。エンドポイントの通信ポートは443です。

```
名前：SG-ForEndpoint-ECStest（任意で入力）
説明：（任意で入力）
インバウンド：ポート 443, IPアドレス → VPC CIDRブロック
アウトバウンド：すべて

```

VPC コンソールの エンドポイント コンソールから「エンドポイントを作成」ボタンを押します。
エンドポイントは以下の４つが必要です。

・com.amazonaws.region.ecr.dkr
・com.amazonaws.region.ecr.api
・com.amazonaws.region.s3　（ECR の後ろで動いているのが S3 のため必要です。）
・com.amazonaws.region.logs　(先ほどタスク定義のところでCloudwatchLogsを取るようにしたため必要ですが、取らないならいりません。)


私は、エンドポイントの名前を分かりやすいようサービスをエンドポイントの名前に入れていますが、名前「Name」は好きなもので構いません。

![スクリーンショット 2025-01-21 232623.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/5479ee56-3cd1-73f4-3d57-95e398c3eed8.png)

![スクリーンショット 2025-01-21 232710.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/0f20307b-c122-cd12-f692-ac397fb4ec38.png)

![スクリーンショット 2025-01-21 232741.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/15e388f8-eac1-dd8c-9918-5854a67051cc.png)

ポリシーの下に作成ボタンがあるため、クリックを押して作成してください。
同じ要領で４つ作ります。S3だけはInterfaceではなくgatewayの設定で作成してください。


![スクリーンショット 2025-01-21 233312.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/fdc34c90-b75d-b435-2fca-0f0dcc0f17e9.png)


## 5. セキュリティグループ作成

ECS用のセキュリティグループとELB用のセキュリティグループを作成します。
記事冒頭の構成図を意識しながら設定してください。

### ELB用のセキュリティグループ
- インバウンド：
    - ルール１ 
        - ポート：80
        - ソース：0.0.0.0/0 （すべて）
    - ルール２
        - ポート：8080
        - ソース：0.0.0.0/0 （すべて）
        - 
- アウトバウンド：
    - すべて許可

### ECS用のセキュリティグループ
- インバウンド：
    - ルール１ 
        - ポート：80
        - ソース：ELB用のセキュリティグループ
    - ルール２
        - ポート：8080
        - ソース：ELB用のセキュリティグループ
        - 
- アウトバウンド：
    - すべて許可


## 6. ELB・ターゲットグループ作成

それでは、ELBを作成していきます。
冒頭の構成図では、80ポートでELBにアクセスするとECSにアクセスでき、8080番ポートでアクセスするとデプロイ前の新しいECSにテスト接続できるようになっていましたが、ECSサービス作成時にELBのターゲットとリスナーの詳細は設定・追加できるため、ここですべてを設定する必要はありません。

ここでは、80ポートリスナー分だけ作成していきます。もちろん現時点では ECS はないため、ターゲットは空の状態で作成する必要がります。


![スクリーンショット 2025-01-21 235506.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/978c83f6-bb48-4396-4b18-1e906962f9f4.png)

![スクリーンショット 2025-01-21 235550.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/8bfeab19-434c-5479-f5d1-aee3067ba887.png)

![スクリーンショット 2025-01-21 235942.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/85099104-65c5-9e87-5da6-24f5240c502b.png)

上記のように、ALB の作成の途中で、デフォルトアクションにターゲットグループを作成する必要があります。この時点だとターゲットグループは作成していないので、「ターゲットグループの作成」ボタンを押してターゲットグループ作成画面に遷移します。

ターゲットタイプは「IPアドレス」で設定して、ターゲットを空の状態で作成します。

![スクリーンショット 2025-01-21 235754.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/cc557c58-3ce9-5ea1-c772-e7a821afe997.png)


![スクリーンショット 2025-01-21 235820.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/3aa4cf81-1715-ae59-80a4-5bbd46f5efa6.png)

![スクリーンショット 2025-01-21 235858.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/5d0a7fc0-1116-cc26-3cdf-06cfce57c484.png)


ここまでで、ターゲットグループの作成は完了です。
先ほどのELBの画面に戻って作成したターゲットグループをデフォルトアクションに設定します。

![スクリーンショット 2025-01-21 235942.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/85099104-65c5-9e87-5da6-24f5240c502b.png)

これで作成ボタンを押して作成を押して、ELBが作成されるまで待ちます。


## 7. ECS Cluster、ECSサービスの作成

ECS Cluster と ECSサービスの関係ですが、Cluster の中に ECS サービスを展開して、その ECS サービスの中でタスクが展開されるという感じです。

ECS Cluster の作成方法は非常に簡単です。以下のように必要事項を記載して作成完了です。

![スクリーンショット 2025-01-22 000633.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/c260b033-86b4-ecd1-d147-59121fa1edf5.png)

#### ECSサービスの作成

ECSサービスを作成するのですが、ここで同時に CodedeployとELBのリスナーの設定もしていきます。
クラスターの「サービス」から「作成」ボタンを押して作成していきます。

![スクリーンショット 2025-01-22 001532.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/e6c2b190-0809-06b1-54e8-52f5be1a22a0.png)

![スクリーンショット 2025-01-22 001821.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/7cdb64d5-0894-0543-cf7d-88b527ebdfad.png)

「3. ECR から ECS のためのタスク定義の作成」章で事前に作成していたタスク定義を選択します。


![スクリーンショット 2025-01-22 001930.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/bcba3b36-82d0-49ef-b3a3-acf1a88b8b40.png)

ここでCodedeployの設定をします。詳細は後ほど、Codedeployで設定していきます。
Codedeployを操作できる権限のIAMロールを事前に作成し、ここで指定します。

![スクリーンショット 2025-01-22 001951.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/42c74ae9-8483-8e5d-b714-5d5550766ac4.png)

ECS作成時にはネットワーキングは必須の設定です。間違えると展開しなおす必要があるので注意してください。

![スクリーンショット 2025-01-22 002015.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/e390eef8-6db4-4d52-44eb-402b543adb7a.png)

ここからはELB設定になります。


![スクリーンショット 2025-01-22 002041.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/e00ce0f3-9364-1a1d-8794-0edd313c3114.png)

既存のリスナーにチェックを付けて、先ほど作成したELBのリスナーを指定します。
そして、「テストリスナーを追加」にチェックを入れると、テスト用のリスナーとターゲットグループを作成できるようになります。「テストリスナーを追加」にチェックを入れ、8080ポートのリスナーを作成します。

![スクリーンショット 2025-01-22 002113.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/3a48c166-0567-ff43-0062-9e8f0467edf8.png)

ターゲットグループを設定します。一つは「6. ELB・ターゲットグループ作成」で事前に作成したターゲットグループ、もうひとつは上記の画像で新たに作成したテスト用リスナーのターゲットグループを作成します。

![スクリーンショット 2025-01-22 002132.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/5dee9be7-7ac8-bccc-aad2-216595854c53.png)

そして、「作成」ボタンを押すと、ECS サービスとその中にタスクが作成されます。実行中になればサービスは正常に動いています。

![スクリーンショット 2025-01-22 002433.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/7aae7017-3d52-df4a-bd44-ca820288218f.png)

ELBのDNSをブラウザで検索すると「hello world」が表示されたので成功です。

![スクリーンショット 2025-01-22 002514.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/6ded4a92-ab5e-3511-cf44-fcb25342ba3f.png)


## 8. Codedeploy でサービスの更新およびテスト

#### 概要

以下は冒頭に見せた構成図です。サービス更新の流れとしては以下のようにしたいともっています。


![スクリーンショット 2025-01-24 005524.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/c6422464-585e-925d-26c4-a607b79d26d7.png)

① サービスを更新すると新しい ECS が作成され、更新前のECSには引き続きアクセスができ、その新しいECSには8080ポートでテストができる。

![スクリーンショット 2025-01-24 010436.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/e757447b-dd4e-cc15-4e96-27b5c19d9fc8.png)

② テストして問題なければ、本番ポートとテストポートを入れ替えて、新しい ECS が80ポートでアクセスできるようになる。まだ、問題が発生する可能性があるので、旧ECSは残している状態。

![スクリーンショット 2025-01-24 010721.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/941fba6c-0a71-eb32-858b-503b2289587c.png)

③ 運用して新しい ECS に問題ないことが分かれば、旧ECSは削除される仕組み

![スクリーンショット 2025-01-24 010954.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/0fc93492-dcac-138d-26c0-1ac337b31329.png)


#### 設定

「7. ECS Cluster、ECSサービスの作成」の章で、ECSサービスを展開するときにCodedeployの設定をしましたので、ECSを作成した時点で以下のように Codedeploy は既に作成されている状態になります。

![スクリーンショット 2025-01-22 002628.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/83aee823-a78c-de30-1630-0967842d69ed.png)

アプリケーション名をクリックして、編集ボタンを押し、設定を見ていきますと、作成された時点では「すぐにトラフィックを再ルーティング」という設定になっています。この設定ですと、ECSサービスを更新したタイミングで、すぐにECSサービスが入れ替わりサイトが更新されてしまって、8080ポートを使ったテスト接続ができません。

なので、ルーティングを一定期間抑えたり、手動でルーティングを行えるように、「トラフィックを再ルーティングするタイミングを指定します」を選択し、時間は１時間を設定します。

これで、ECSを更新したら１時間ルーティングを待つようになります。この設定にすると手動でルーティングも可能です。

![スクリーンショット 2025-01-22 002709.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/dc6eb0f5-0404-f090-846a-1f0f0ba32b24.png)

#### デプロイの挙動確認

上記の設定が完了したら、ECSサービスを更新していきましょう。


まずはローカルのコードを修正してECRにプッシュしなおします。
以下が新しいwebサイトのコードです。「hello world」を「hello world updated!!」に変えただけです。


```
from flask import Flask
from flask import g
from flask import render_template
from flask import request
from flask import Response

app = Flask(__name__)
@app.route('/')
def hello_world():
  return "hello world updated!!"

def main():
  app.debug = True
  app.run(host="0.0.0.0", port=80)

if __name__=='__main__':
  main()
```

「2. Dockerfileとアプリケーションファイルを ECR にプッシュ」の章と同じように4つのコマンドをローカルPCの Dockerfile がある階層で実行し、ECR にプッシュします。

すると以下のように、新しく上げたものがlatestとなり、イメージが2つになったことが分かります。

![スクリーンショット 2025-01-22 003229.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/08c0f6c1-e605-7806-2d50-ec82f6d5d8db.png)

次にタスク定義を更新します。
既に作成していたタスク定義の中に入ると「新しいリビジョンの作成」というボタンがありますので、ここで新バージョンの定義を作成します。

![スクリーンショット 2025-01-22 003339.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/4e6b4041-e905-c1a8-1e4e-73a57eb4fb76.png)

「新しいリビジョンの作成」を押したら、3章の「3. ECR から ECS のためのタスク定義の作成」と同様 ECR のイメージ URI を入力する欄に新しい ECR イメージの URI を入力して作成すると、上記の画面に
「taskdef-ECStest:2」が作成されます。（すみません、キャプチャ取り忘れました）

ECSの画面に戻って、「更新」をクリックすると、ECSサービスの設定画面が表示されますので、
以下のようにリビジョンを「2(最新)」に設定します。他は変更せずに更新ボタンを押します。

![スクリーンショット 2025-01-22 003711.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/72595cb4-883a-692e-7ade-a0e18af444d1.png)

すると以下のようにデプロイIDが表示されますので、クリックすると、Codedeployの画面に行きます。

![スクリーンショット 2025-01-22 012844.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/c381d1e3-743e-4a58-b505-a4ae3e45ad45.png)


![スクリーンショット 2025-01-22 012918.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/2f06c58f-e7bc-9380-3307-ed304c10a8e1.png)

しばらく待つとステップ２まで進みます。
この段階では以下の画像のように80ポートは更新前のECSで、8080ポートで新しいECSにテスト接続ができます。

右上に「トラフィックの・・・」で見切れてますが、「トラフィックの再ルーティング」というボタンがあって、これをクリックすると、テスト（新しいECSサービス）と本番（古いECSサービス）が入れ替わります。

![スクリーンショット 2025-01-22 013106.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/0553e670-a3d1-04f3-187e-d4fc6f6235f5.png)

![スクリーンショット 2025-01-22 013134.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/953c7ca0-0f8a-1024-4efa-9b889c41f422.png)

![スクリーンショット 2025-01-22 013155.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/c3b235cc-ff7f-6795-6b31-6840ea18f229.png)


「トラフィックの再ルーティング」をクリックするとステップ４まで進み、新しいECSが80ポートになり、古いECSが8080ポートで残っている状態になります。ここで「デプロイを停止してロールバック」を押すと古いECSに戻ります。

今回はこのまま「元スタックの終了」を押して、古いものを削除します。

![スクリーンショット 2025-01-22 013236.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/6a605a67-03e2-dc71-651b-6bb604e23c47.png)
![スクリーンショット 2025-01-22 013308.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/998cb0dc-1ef3-eb84-ea26-08918e1a9b4d.png)
![スクリーンショット 2025-01-22 013335.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1260979/b627a851-1e9a-d315-09c6-7f460ca2b628.png)


以上が、Codedeployを使用したECS更新の流れになります。
本記事はこれですべて終了になります。最後までご覧いただきありがとうございましｔ