Docker初心者がPythonで作った妄想マイクロサービスをFargateにデプロイするまで

## 概要
マイクロサービスっぽいアプリをPython, MySQL, Dockerで作ってみて、Fargateにデプロイしてみる記事です。

AWSでいま最もアツいと(一部で)言われるFargateを使ってみたかったのと、マイクロサービスアーキテクチャへの興味でやってみました。とはいえ、マイクロサービスをちゃんと扱ったことも無いので、「こんな感じなんだろうな〜」という妄想のアーキテクチャです。もし誤りなどありましたら、ご意見いただけると嬉しいです。

なお、AWSならRDSもありますが、そもそもコンテナも初心者なので、勉強も兼ねてMySQLコンテナを使ってます。

## そもそもマイクロサービスやFargateとは
いずれもネットに解説が多数ありますが、簡単に自分の理解を記載します。

### マイクロサービス
- アプリケーションが__複数の小さなサービスから成り立ち、それぞれが疎結合__である。
  - 疎結合ゆえ、改修が各サービス内で完結する。
  - 各サービスを複数チームで開発できるため、各チームが得意な言語やフレームワークを利用できる。（逆にアプリ全体で統一する場合もあり）
- 小さなサービスを実現するための技術として**コンテナ**が使われる。
  - コンテナを管理するツールとして、KubernetesやAWS ECSがある。
- サービス間のデータ通信では、**RESTやgRPC**が主に使われる。メッセージング技術(Kafkaとか)が使われることも。

### Fargate
- 正確にはECSおよびEKSと合わせて使う。
- ECSなどのコンテナオーケストレーションツールを使えば、コンテナの管理は行えるが、コンテナをデプロイする先であるホスト(サーバ)は別で管理する必要がある。
  - 例えば、 コンテナがスケーリングしてホストへの負荷が高まった場合、ホストもスケーリングする必要があり、こういった管理が使う側としては大変
- Fargateは、この**ホストの管理をAWS側が行うマネジメント型のサービス**。例にあげたスケーリングも自動で行われるため、**利用者はホストの管理を意識する必要がなくなる**。

だいたいこんな理解です。つまり今回は、__Dockerで構築したマイクロサービスをAWSのマネジメントサービスであるFargateにデプロイしてみる__、ということをやります。

## アプリのイメージ図
![arch03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451175/c4d69ae4-aea0-7018-183d-435b11450e46.png)


- マイクロサービスは、AplとBackEndの2つを用意します。
- サービス間の通信はHTTP(REST)
- クライアントからAplサービスのAPIにアクセス。AplサービスからBackEndサービスのAPIにアクセスすることで、クライアントからDBまでアクセスするイメージ
- Pythonを3.6と3.8で分けているのは、「Dockerならこんなこともできる」というのを体感したかっただけで、それ以上の理由はありません。両方3.6でも問題無いです。

## 記事の構成とゴール
まずはマイクロサービスをローカルをホストとしてデプロイして、その後Fargateに持っていきます。Fargateにデプロイが成功した後、外部から2つのマイクロサービスにHTTPで疎通できることをゴールとします。

## 環境
- 開発環境
  - Python 3.6 / 3.8
  - MySQL 5.7
  - Docker
  - Mac OS
- 前提知識
  - Pythonのソースが読める
  - SQLに関する基礎知識
  - docker-composeを使ったことがある
  - AWSのサービス名でどんなものかなんとなくわかる


## 1. まずはMacをホストにして起動

ローカルであるMac上にアプリをデプロイします。ソースなどは以下の通りです。

### フォルダ構成

3つのコンテナ(apl-service, db-service, mysql)ごとにフォルダを作成してます。

```bash

.
├── apl-service
│   ├── Dockerfile
│   └── src
│       ├── results.py
│       └── server.py
├── db-service
│   ├── Dockerfile
│   └── src
│       ├── server.py
│       └── students.py
├── docker-compose.yml
└── mysql
    ├── Dockerfile
    └── db
        ├── mysql_data
        └── mysql_init
            └── setup.sql
```

### MySQL関連

```dockerfile:mysql/Dockerfile
FROM mysql/mysql-server:5.7

RUN chown -R mysql /var/lib/mysql && \
    chgrp -R mysql /var/lib/mysql
```

```sql:setup.sqlと実際に登録されたデータ
create table students (id varchar(4), name varchar(20), score int);
insert into students values ('1001', 'Alice', 60);
insert into students values ('1002', 'Bob', 80);
commit;
mysql> select * from DB01.students;
+------+-------+-------+
| id   | name  | score |
+------+-------+-------+
| 1001 | Alice |    60 |
| 1002 | Bob   |    80 |
+------+-------+-------+
```

### db-service関連

```dockerfile:db-service/Dockerfile
FROM python:3.6

# コンテナ上のワークDIR
WORKDIR /usr/src/

# ライブラリのインストール
RUN pip install flask mysql-connector-python

CMD python ./server.py

```

```python:db-service/src/server.py
from flask import Flask, request, abort, render_template, send_from_directory
from students import get_students

app = Flask(__name__)


@app.route('/students', methods=['GET'])
def local_endpoint():
    return get_students()


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5001)

```

```python:db-service/src/students.py
import json
import mysql.connector as mydb


def get_students():
    conn = mydb.connect(user="user", passwd="password",
                        host="mysql", port="3306")

    cur = conn.cursor()
    sql_qry = 'select id, name, score from DB01.students;'
    cur.execute(sql_qry)

    rows = cur.fetchall()

    results = [{"id": i[0], "name": i[1], "score": i[2]} for i in rows]
    return_json = json.dumps({"students": results})
    cur.close()
    conn.close()

    return return_json

```

- 簡単にソース解説
  - Dockerコンテナ起動時に、server.pyを実行します。
  - server.pyではFlaskを使ってサーバを立てています。```/students```というリソースにGETでアクセスしてきたら、students.pyからimportしたget_studentsを実行して、結果を返すAPIです。
  - get_studentsでは、MySQLコンテナに接続して、DBからselectした結果をrowsに格納します。rowsから内包表記でdictにして、その後jsonに変換して値をリターンしています。
  - ローカルへのデプロイなら`links`を使わなくてもコンテナ間で上手く繋がります。というか、`links`は古い手法として、あまり最近は使われてないようですね...

### apl-service関連

```dockerfile:apl-service/Dockerfile
FROM python:3.8

# コンテナ上のワークDIR
WORKDIR /usr/src/

# ライブラリのインストール
RUN pip install requests flask

CMD python ./server.py
```

```python:apl-service/src/server.py
from flask import Flask, request, abort, render_template, send_from_directory
from results import get_results

API_URI = 'http://db-service:5001/students'

app = Flask(__name__)


@app.route('/results', methods=['GET'])
def local_endpoint():
    return get_results(API_URI)


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5002)

```

```python:apl-service/src/results.py
import json
import requests


def get_results(uri):
    r = requests.get(uri)
    students = r.json()["students"]
    return_students = []
    for student in students:
        if student["score"] > 70:
            student.setdefault("isPassed", True)
            return_students.append(student)
        else:
            student.setdefault("isPassed", False)
            return_students.append(student)

    return_json = json.dumps({"addedStudents": return_students})

    return return_json

```
- 簡単にソース解説
  - server.pyあたりはdb-serviceとほぼ同じ。```/results```というリソースにGETでアクセスしてきたら、results.pyからimportしたget_resultsを実行して、結果を返すAPIです。
  - 違いとしては、get_resultsからdb-serviceのAPIにアクセスするため、URIを設定しています。
  - get_resultsでは、db-seriveから取得した値をstudentsに格納します。これをforで回して、各scoreの値が70より大きければ`"isPassed"`にTrue, それ以外はFalseを設定して、jsonの要素を追加します。

### docker-compose

```dockerfile:docker-compose.yml
version: '3'

services:
  mysql:
    container_name: mysql
    build:
      context: .
      dockerfile: ./mysql/Dockerfile
    hostname: mysql
    ports:
      - "3306:3306"
    volumes:
      - ./mysql/db/mysql_init:/docker-entrypoint-initdb.d
      - ./mysql/db/mysql_data:/var/lib/mysql
    environment:
      MYSQL_USER: user
      MYSQL_PASSWORD: password
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: DB01
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci --skip-character-set-client-handshake
 
  db-service:
    build:
      context: .
      dockerfile: ./db-service/Dockerfile
    container_name: db-service
    ports:
      - "5001:5001"
    volumes:
      - ./db-service/src/:/usr/src/

  apl-service:
    build:
      context: .
      dockerfile: ./apl-service/Dockerfile
    container_name: apl-service
    ports:
      - "5002:5002"
    volumes:
      - ./apl-service/src/:/usr/src/

```
- 簡単にソース解説
  - mysqlのvolumesでは、docker-entrypoint-initdb.dに先程のsetup.sqlが格納されるよう設定しています。MySQLでは、初回起動時に、docker-entrypoint-initdb.d配下にあるSQLが実行されます。要するに、初期データです。
 

### デプロイして実行
結果は省略しますが、docker-composeコマンドでデプロイします。

```bash:build&up
$ docker-compose build
$ docker-compose up
```

デプロイが成功したら、疎通確認として各マイクロサービスにHTTPでアクセスしてみましょう。ソースの通り、db-serviceは5001, apl-serviceは5002のポートを使っています。

```bash:db-serviceにアクセス
$ curl http://127.0.0.1:5001/students
{"students": [{"id": "1001", "name": "Alice", "score": 60}, {"id": "1002", "name": "Bob", "score": 80}]}
```

```bash:apl-serviceにアクセス
$ curl http://127.0.0.1:5002/results
{"addedStudents": [{"id": "1001", "name": "Alice", "score": 60, "isPassed": false}, {"id": "1002", "name": "Bob", "score": 80, "isPassed": true}]}

```

これで土台となる、（妄想の）マイクロサービスをローカルにデプロイできました。
apl-serviceのAPIの結果が返ってきているので、apl-serviceとdb-serviceのマイクロサービス間の疎通が取れていることも確認できます。

早速、これをFargateに持っていきます！が、ここからが長かった...

## 2. 改めて最終版のイメージ図

Fargateへデプロイすることを踏まえて最終版の構成図です。
![arch04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451175/399da7c6-b08b-29f6-8c09-934077fff643.png)



ポイントとなるのは以下の点です。

 - 1マイクロサービスを1つのFargateにデプロイすること。
 - タスク内でコンテナ間通信(db-service⇔mysql)があること。
 - タスク間通信(apl-service⇔db-service)があること。

これらを踏まえて、デプロイしていきます。

## 3. ECRへの登録
まずは、作成した3つのdockerイメージ(apl-service, db-service, mysql)をECRにpushします。ECRはUIがわかりやすいので、AWSのコンソールから「リポジトリを作成」と押してそのまま進めていけば、pushできると思います。

※ 1で作成した起動したコンテナは一度stopして`docker rm`しておいてください。


## 4. Fargateへのデプロイ前に

ECSでは、docker-compose.ymlを使うことが可能です。ecs-cliを設定した後、以下のチュートリアルに沿って、Fargateにデプロイしていきますが、docker-compose.yml等を修正する必要があります。色々修正ポイントを上げていきますが、最終版のソースは記事の最後に掲載してますので、忙しい方はそちらを参照ください。


[チュートリアル: Amazon ECS CLI を使用して Fargate タスクのクラスターを作成する](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ecs-cli-tutorial-fargate.html#ECS_CLI_tutorial_fargate_configure)

また、今回のアプリで言えば、5001, 5002のポートも使っているので、ステップ3のセキュリティグループの設定でこれらも許可する必要があります。

```bash:5001を許可する場合
$ aws ec2 authorize-security-group-ingress --group-id <security-group-id> --protocol tcp --port 5001 --cidr 0.0.0.0/0 --region <region>
```

### docker-compose.ymlの分割
   - 今回、2つのマイクロサービスを2つのタスクとしてデプロイします。このため、docker-compose.ymlも2つに分割する必要があります。なお、デプロイのコマンド実行も2回必要となります。
   - backend(db-service, mysql)と、apl(apl-service)で分割します。
   - 同じ名前のdocker-compose.ymlファイルが2つできることになるので、適当にフォルダを分けておきましょう。

```bash:例）フォルダ「aws_work」に格納してbackendというタスク名でデプロイ
$ pwd
/Users/<省略>/aws_work
$ ls docker-compose.yml   # db-service + mysqlのdocker-compose.yml
docker-compose.yml
$ ecs-cli compose --project-name backend service up ~
```

```bash:例）フォルダ「aws_work」に格納してaplというタスク名でデプロイ
$ pwd
/Users/<省略>/aws_work2
$ ls docker-compose.yml   # apl-serviceのdocker-compose.yml
docker-compose.yml
$ ecs-cli compose --project-name apl service up ~
```
※ この時点ではデプロイしても上手くいきません。

### ECRにpushしたimageの指定
   - さきほどpushしたimageをDockerfileの代わりに指定します。`build`, `container_name`, `hostname`は不要なので、代わりに`image`を指定します。
   - 以下の例だと、mysqlコンテナのECRのリポジトリ名は`python-mysql/mysql`としています。
   - db-service, apl-serviceも同様に`image`に置き換えます。

```dockerfile:例）docker-compose.ymlの修正ポイント(imageの設定)
  mysql:
    image: <aws_account_id>.dkr.ecr.<region>.amazonaws.com/python-mysql/mysql
```

### volumesを利用しないよう修正
 - Fargateでは永続ストレージボリュームがサポートされません。難しく聞こえますが、docker-compose.ymlのvolumesが使えないということです。そのため、server.pyとかのソースがコンテナに反映されません。
 - というわけで、docker-compose.ymlからvolumesをコメントアウト（もしくは物理削除）して、Dockerfile内にCOPYを記載するようそれぞれ修正します。
 - db-service, apl-serviceも同様の修正を行います。
 - Dockerfileを修正したので、再度ECRにpushする必要があります。

```dockerfile:例）docker-compose.ymlの修正ポイント
    # volumes:
    #   - ./db-service/src/:/usr/src/
```

```dockerfile:例）db-service/Dockerfileの修正ポイント
# コンテナにソースをコピー
COPY ./src/server.py /usr/src/server.py 
COPY ./src/students.py /usr/src/students.py 
```

### コンテナログの設定
   - 上記チュートリアルにも記載されていますが、Fargateタスクのベストプラクティスとして、コンテナログを設定します。ここではpython-dockerというロググループ名です。
   - db-service, apl-serviceにも同様に設定します。

```dockerfile:例）docker-compose.ymlの修正ポイント(コンテナログ)
  mysql:
    logging:
      driver: awslogs
      options: 
        awslogs-group: python-docker
        awslogs-region: <region>
        awslogs-stream-prefix: mysql
```

また、今回は修正していませんが、Fargateでは`ports`がホストとコンテナ側で同じ番号になる必要がある点も注意事項です。例えば、ローカルでは`80:5001`という指定はできますが、Fargateでは`5001:5001`と指定しないとエラーとなります。

4点ほど修正しましたが、これだけだとまだ上手くいきません。


## 5. 更に修正してFargateへデプロイ

### 5-1. コンテナ間通信とタスク間通信
ここからは、先述したタスク内でのコンテナ間通信や、タスク間通信を実現するために必要となる修正です。まずは、コンテナ間通信を実現するため、db-serviceとmysqlについて記載します。

#### デプロイしてHTTP通信してみる

さきほどまでの修正をデプロイした後、```ecs-cli compose ~ service ps ~```のコマンドでステータスを確認すると、一応`RUNNING`になっていると思います。
実際に、立ち上げ後psをした結果です。

```bash
$ ecs-cli compose --project-name backend service ps --cluster-config sample-config --ecs-profile sample-profile
Name                                              State    Ports                         TaskDefinition    Health
<task-id>/db-service   RUNNING  XXX.XXX.XXX.XXX:5001->5001/tcp  backend:12  UNKNOWN
<task-id>/mysql      RUNNING  XXX.XXX.XXX.XXX:3306->3306/tcp  backend:12  UNKNOWN
```

グローバスのIPアドレスが表示されるため、Macからdb-serviceにGETで通信してみますが、先述の通りまだ上手くいきません。

```bash
$ curl http://XXX.XXXX.XXX.XXX:5001/students
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>
```

### 5-2. コンテナ間通信のための修正

ECSのコンソールにあるからLogsタブなどでログを確認すると、名前解決できていない旨のエラーが出てると思います。今回でいったら、db-serviceからmysqlのホスト名が見つからない、というエラーが出ます。要するに、コンテナ間で通信できてない、ということです。
なお、ローカルへのデプロイ時に書きましたが、今回はLinksを使っていません。また、FargateタイプではそもそもLinksがサポートされていません。

じゃあどうやって解決するのかというと、Fargateのコンテナ間通信は、同一タスク上であれば、ポート番号でアクセス可能となります。というわけで、students.pyのソースを以下の通り変更します。また、今更ですがホスト名をオンコーディングは微妙なので、docker-compose.ymlから環境変数で渡すよう合わせて修正します。

```python:db-service/src/students.pyの修正ポイント
import json
import mysql.connector as mydb
import os  # 追加

def get_students():
    # 環境変数から取得するよう修正
    conn = mydb.connect(user=os.environ['DB_USER'], passwd=os.environ['DB_PASS'],
                        host=os.environ['DB_HOST'], port=os.environ['DB_PORT'])
```

```dockerfile:docker-compose.ymlの修正ポイント(db-service)
 db-service:
    - 省略 -
    environment:
      DB_HOST: 127.0.0.1
      DB_USER: "user"
      DB_PASS: "password"
      DB_PORT: "3306"
```

再度、ECRにpushしてデプロイします。
なお、一度Fargateにデプロイしている場合、変更を反映するには、チュートリアルにもある```ecs-cli compose ~ service down ~```のコマンドで一度落として、```ecs-cli compose ~ service up ~```のコマンドで立ち上げ直します。
※ downのコマンド完了後も落ちきるまでは少し時間がかかるようです。


#### 再度デプロイでdb-serviceは疎通！

無事、Macからdb-serviceにHTTP通信できていることが確認できます。
通信経路をおさらいすると、以下のとおりです。
1. Fargate上のdb-serviceというマイクロサービスに外部(Mac)からHTTP通信
2. db-serviceとMySQLがコンテナ間で通信して、データを取得
3. db-serviceを経由して、リクエスト元に値が返ってくる

```bash
$ curl http://XXX.XXX.XXX.XXX:5001/students
{"students": [{"id": "1001", "name": "Alice", "score": 60}, {"id": "1002", "name": "Bob", "score": 80}]}
```

※ バックエンドのサービスに直接通信できるのはセキュリティ的にちょっと...思われる方もいると思いますが、今回はわかりやすさを重視して通信可能としています。多分、本番運用ではセキュリティグループとかで設定するのかなあという妄想。


### 5-3. タスク間通信のための修正

続いて、apl-serviceを疎通させます。先述の通り、apl-serviceはdb-serviceのAPIを実行するため、別のタスクと通信させる必要があります。Fargateでこれを実現するには、サービス検出を有効にして起動を行います。
サービス検出の仕組みは、以下のClassmethodさんの記事が非常にわかりやすいので省略しますが、要するにRoute53のAレコードがマイクロサービス間に自動作成され、名前検出が可能となるということです。

[ECSのサービスディスカバリーが東京にやってきて、コンテナ間通信の実装が簡単になりました！](https://dev.classmethod.jp/cloud/aws/ecs-service-discovery/)

今回は、ecs-cliを使っているので、AWS公式の以下チュートリアルを参考にコマンドでデプロイしていきます。

[チュートリアル : Amazon ECS CLI を使用して サービス検出 を使用する Amazon ECS サービスを作成する](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ecs-cli-tutorial-servicediscovery.html)

なお、サービス検出時のホスト名は`service_name.namespace`となります。今回、サービス名をbackend、名前空間をsampleとして作成するため、apl-serviceからdb-serviceにアクセスするためには、`backend.sample`というホスト名を設定する必要があります。
というわけで、db-serviceの時と同様に、環境変数からホスト名を設定するよう修正します。

```python:apl-service/src/server.pyの修正ポイント
from flask import Flask, request, abort, render_template, send_from_directory
from results import get_results
import os  # 追加

# 環境変数から取得するよう修正
API_URI = 'http://' + os.environ['BACKEND_HOST'] +':' + os.environ['BACKEND_PORT'] + '/students'
```

```dockerfile:docker-compose.ymlの修正ポイント(apl-service)
 apl-service:
    - 省略 -
    environment:
      BACKEND_HOST: "backend.sample"
      BACKEND_PORT: "5001"
```

これで準備が整いました。再度apl-serviceのコンテナをECRにpushします。
既にdb-service, mysqlが起動している場合、同じ名前空間上を設定してデプロイする必要があるので、一度サービスを落とします。その後、それぞれのdocker-compose.ymlのファイルがあるフォルダで、名前空間のオプションを追加してデプロイします。
なお、サービス名はそれぞれbackend, aplとします。

```bash:backendサービスのデプロイ
$ ecs-cli compose --project-name backend service up --private-dns-namespace sample --vpc <vpc-id> --enable-service-discovery --ecs-profile sample-profile
```

```bash:aplサービスのデプロイ
$ ecs-cli compose --project-name apl service up --private-dns-namespace sample.com --vpc <vpc-id> --enable-service-discovery --ecs-profile sample-profile
```

注意事項として、チュートリアルのデプロイコマンドに、`--ecs-profile`も付けてあげる必要があります。


#### apl-serviceも疎通！

```bash
$ curl http://XXX.XXX.XXX.XXX:5002/results
{"addedStudents": [{"id": "1001", "name": "Alice", "score": 60, "isPassed": false}, {"id": "1002", "name": "Bob", "score": 80, "isPassed": true}]}
```
これでようやく、apl-serviceも疎通確認ができました。通信経路をおさらいすると、以下のとおりです。
1. Fargate上のapl-serviceに外部(Mac)からHTTP通信
2. apl-serviceがdb-serviceのAPIを実行
3. db-serviceとMySQLがコンテナ間で通信して、データを取得
4. db-service, apl-serviceを経由して、リクエスト元に値が返ってくる


## 6. ソースとデプロイコマンドのまとめ
色々修正したので、変更があったソースとデプロイコマンドをまとめます。なお、regionはap-northeast-1でやりましたが、別を使われてる方は変更の必要があるのでご注意ください。

### MySQL関連

```dockerfile:mysql/Dockerfile
FROM mysql/mysql-server:5.7

COPY ./db/mysql_init/setup.sql /docker-entrypoint-initdb.d/setup.sql

RUN chown -R mysql /var/lib/mysql && \
    chgrp -R mysql /var/lib/mysql
```
setup.sqlは変更無いので省略

### db-service関連

```dockerfile:db-service/Dockerfile
FROM python:3.6

# コンテナ上のワークDIR
WORKDIR /usr/src/

# ライブラリのインストール
RUN pip install flask mysql-connector-python

# コンテナにソースをコピー
COPY ./src/server.py /usr/src/server.py 
COPY ./src/students.py /usr/src/students.py 

CMD python ./server.py
```

`db-service/src/server.py`は変更無いので省略


```python:db-service/src/students.py
import json
import mysql.connector as mydb
import os


def get_students():
    conn = mydb.connect(user=os.environ['DB_USER'], passwd=os.environ['DB_PASS'],
                        host=os.environ['DB_HOST'], port=os.environ['DB_PORT'])

    cur = conn.cursor()
    sql_qry = 'select id, name, score from DB01.students;'
    cur.execute(sql_qry)

    rows = cur.fetchall()

    results = [{"id": i[0], "name": i[1], "score": i[2]} for i in rows]
    return_json = json.dumps({"students": results})
    cur.close()
    conn.close()

    return return_json
```

### apl-service関連

```dockerfile:apl-service/Dockerfile
FROM python:3.8

# コンテナ上のワークDIR
WORKDIR /usr/src/

# ライブラリのインストール
RUN pip install requests flask

# コンテナにソースをコピー
COPY ./src/server.py /usr/src/server.py 
COPY ./src/results.py /usr/src/results.py 

CMD python ./server.py
```

```python:apl-service/src/server.py
from flask import Flask, request, abort, render_template, send_from_directory
from results import get_results

API_URI = 'http://db-service:5001/students'

app = Flask(__name__)


@app.route('/results', methods=['GET'])
def local_endpoint():
    return get_results(API_URI)


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5002)

```

`apl-service/src/results.py`は変更無いので省略

### docker-compose (db-service, mysql)

```dockerfile:docker-compose.yml
# aws_account_id, regionは実態に合わせて修正必要
version: '3'

services:
  mysql:
    image: <aws_account_id>.dkr.ecr.ap-northeast-1.amazonaws.com/python-mysql/mysql
    ports:
      - "3306:3306"
    environment:
      MYSQL_USER: user
      MYSQL_PASSWORD: password
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: DB01
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci --skip-character-set-client-handshake
    logging:
      driver: awslogs
      options: 
        awslogs-group: python-docker
        awslogs-region: ap-northeast-1
        awslogs-stream-prefix: mysql

  db-service:
    image: <aws_account_id>.dkr.ecr.ap-northeast-1.amazonaws.com/python-mysql/db-service
    ports:
      - "5001:5001"
    environment:
      DB_HOST: 127.0.0.1
      DB_USER: "user"
      DB_PASS: "password"
      DB_PORT: "3306"
    logging:
      driver: awslogs
      options: 
        awslogs-group: python-docker
        awslogs-region: ap-northeast-1
        awslogs-stream-prefix: db-service
```
 

### docker-compose (apl-service)

```dockerfile:docker-compose.yml
# aws_account_id, regionは実態に合わせて修正必要
version: '3'

services:
  apl-service:
    image: <aws_account_id>.dkr.ecr.ap-northeast-1.amazonaws.com/python-mysql/apl-service
    ports:
      - "5002:5002"
    environment:
      BACKEND_HOST: "backend.sample"
      BACKEND_PORT: "5001"
    logging:
      driver: awslogs
      options: 
        awslogs-group: python-docker
        awslogs-region: ap-northeast-1
        awslogs-stream-prefix: apl-service
```

* ecs-params.ymlはチュートリアル通り作成すればOKなので省略

### デプロイコマンド
チュートリアル(Amazon ECS CLI を使用して Fargate タスクのクラスターを作成する)のステップ3から主要な部分を載せていきますが、コメントが付いてる箇所が変更点です。括弧やregionは実際の値に置き換えてください。

```bash:ステップ3
$ ecs-cli up --cluster-config sample-config --ecs-profile sample-profile
$ aws ec2 describe-security-groups --filters Name=vpc-id,Values=<vpc-id> --region ap-northeast-1
$ aws ec2 authorize-security-group-ingress --group-id <security-group-id> --protocol tcp --port 80 --cidr 0.0.0.0/0 --region ap-northeast-1
# 5001, 5002も許可
$ aws ec2 authorize-security-group-ingress --group-id <security-group-id> --protocol tcp --port 5001 --cidr 0.0.0.0/0 --region ap-northeast-1
$ aws ec2 authorize-security-group-ingress --group-id <security-group-id> --protocol tcp --port 5002 --cidr 0.0.0.0/0 --region ap-northeast-1
```

```bash:ステップ5
# backendのデプロイ - サービス検出のため名前空間の作成オプションを追加
$ ecs-cli compose --project-name backend service up --create-log-groups --cluster-config sample-config --private-dns-namespace sample --vpc <vpc-id> --enable-service-discovery --ecs-profile sample-profile
# aplのデプロイ - backendと同様
$ ecs-cli compose --project-name apl service up --create-log-groups --cluster-config sample-config --private-dns-namespace sample --vpc <vpc-id> --enable-service-discovery --ecs-profile sample-profile
```

※ デプロイを2つ行うので、以降のコマンドも2つ実行

```bash:ステップ6
$ ecs-cli compose --project-name backend service up --private-dns-namespace sample --vpc <vpc-id> --enable-service-discovery --ecs-profile sample-profile
$ ecs-cli compose --project-name apl service up --private-dns-namespace sample.com --vpc <vpc-id> --enable-service-discovery --ecs-profile sample-profile
```

```bash:ステップ10
$ ecs-cli compose --project-name backend service down --cluster-config sample-config --ecs-profile sample-profile
$ ecs-cli compose --project-name apl service down --cluster-config sample-config --ecs-profile sample-profile
$ ecs-cli down --force --cluster-config sample-config --ecs-profile sample-profile
```

## 学んだこと（ハマったこと）
- Fargateでのマイクロサービスなアプリの利用イメージ
- ECSにおけるタスク内通信、タスク間通信の仕組みや方法
- Fargateでは永続ストレージボリュームがサポートされないなど、制約事項いろいろ

## 最後に
フルスクラッチでマイクロサービスとかも作った分、Fargateだけじゃなくマイクロサービス全般について理解が深まったような気がします。
自分がハマったこととかを色々書いたので、構成とかわかりにくい記事になったかもしれません。長くなってしまいましたが、読んでいただきありがとうございました。指摘や質問などもらえると、とてもありがたいです。

## 参考
 - [docker composeを利用して、python（jupyter）+MySQL環境を構築](https://blowup-bbs.com/docker-compose-python-jupyter-mysql/)
 - [AWS Fargateでコンテナ間通信させたいとき](https://qiita.com/taishin/items/84122ad85bedf8b8a682)
 - [dockerでmysqlを使う](https://qiita.com/astrsk_hori/items/e3d6c237d68be1a6f548)
 - [AWS Fargate](https://qiita.com/propella/items/d261879e89774a06e2bb)
 - [今日から始めるDocker【基礎概念編】](https://qiita.com/yu-croco/items/bf54f007c84aba7c8da6)
 - [Amazon EC2 Container Service(ECS)の概念整理]()
 - その他AWS公式チュートリアルやブログなど