DRF+Nuxt+MySQLのローカル・ECS環境構築

DRF+Nuxt+MySQLな環境を作るための備忘録
作業ログ的な感じなので説明省いてますので質問がある方はコメントください。

## 本記事のポイント
1. ローカルでDRF（Django REST framework） + Nuxt + MySQLの環境構築
1. Docker Compose for ECSでDRF（Django REST framework） + Nuxt + RDSの環境構築
1. ECSのコンテナにSSHできるようにする。ECS Exec
1. ALBを使ってHTTPSアクセスできるようにする。

Docker Compose for ECSは構築してしまえば、１コマンドでECSを起動できるのでオススメ
ただしSSH接続やALBを含めた構築はAWS周りの知識、特にCloudFormationが必要なので結構大変です。。。
Qiitaやネット上にほとんど情報がないので自分と他のエンジニアのために記録を残す。グッドラック
※ DRFやNuxtのインストールやセットアップは省く

## ローカル対応

### 各種コンテナ用のDockerファイル作成

#### MySQL
./dockerfiles/mysql_docker/dockerfile
```bash
FROM mysql:latest

COPY ./mysql/my.cnf .

EXPOSE 3306
```

#### DRF（Django REST framework）
./dockerfiles/django_docker/dockerfile
```bash
FROM python:3.7
ENV PYTHONUNBUFFERED 1

RUN mkdir /code
WORKDIR /code

COPY ./django/ /code/

RUN pip install --upgrade pip
RUN pip install -r requirements.txt
```

#### Nuxt
./dockerfiles/nuxt_docker/dockerfile
```bash
FROM node:16-alpine

RUN mkdir -p /code
COPY ./nuxt/ /code/

WORKDIR /code

ENV NODE_ENV=development

RUN yarn install
RUN yarn add @nuxtjs/axios
RUN yarn build

EXPOSE 80
```

docker-compose-local.yml

```bash
# ローカル用
services:
  db:
    container_name: mysql
    build:
      context: .
      dockerfile: ./dockerfiles/mysql_docker/dockerfile
    environment:
      MYSQL_DATABASE: xxx
      MYSQL_USER: xxx
      MYSQL_PASSWORD: xxx
      MYSQL_ROOT_PASSWORD: xxx
      TZ: 'Asia/Tokyo'
    command: --default-authentication-plugin=mysql_native_password
    ports:
      - 3306:3306
    volumes:
      - mysqldata:/var/lib/mysql
      - ./mysql/conf.d:/etc/mysql/conf.d
    restart: always

  web:
    container_name: django
    build:
      context: .
      dockerfile: ./dockerfiles/django_docker/dockerfile
    command: bash -c "sh entry_point.sh && python manage.py runserver 0.0.0.0:8000"
    volumes:
      - ./django:/code
    ports:
      - "8000:8000"
    depends_on:
      - db
    links:
      - db

  front:
    container_name: nuxt
    build:
      context: .
      dockerfile: ./dockerfiles/nuxt_docker/dockerfile
    command:
      yarn dev
    volumes:
      - ./nuxt:/code
    ports:
      - "80:80"
    depends_on:
      - db
    tty: true

volumes:
  mysqldata:
```

### イメージ作成・コンテナ起動

```bash
$ docker-compose -f docker-compose-local.yml build --no-cache
$ docker-compose -f docker-compose-local.yml up -d
```

## ECR対応

### リポジトリ作成、デプロイ

プロファイル作成

※ IAMでグループ・ユーザー追加し予めAccess Key ・Secret Access Keyを取得

```bash
$ aws configure --profile xxx
AWS Access Key ID [None]: xxx
AWS Secret Access Key [None]: xxx
Default region name [None]: ap-northeast-1
Default output format [None]:
```

コンテキスト作成

```bash
$ docker context create ecs xxx
```

ECRリポジトリ作成

```bash
$ aws ecr create-repository --repository-name xxx_front --region ap-northeast-1 --profile xxx
$ aws ecr create-repository --repository-name xxx_web --region ap-northeast-1 --profile xxx
```

タグ

※ 予めローカルでdocker-compose up -build でイメージ作成

```bash
$ docker tag xxx_front:latest xxx.dkr.ecr.ap-northeast-1.amazonaws.com/xxx_front:latest
$ docker tag xxx_web:latest xxx.dkr.ecr.ap-northeast-1.amazonaws.com/xxx_web:latest
```

ログイン

```bash
$ aws ecr get-login-password --region ap-northeast-1 --profile xxx | docker login --username AWS --password-stdin xxx.dkr.ecr.ap-northeast-1.amazonaws.com
Login Succeeded
```

デプロイ

※ タグは作り直さないと反映されないので注意

```bash
$ docker push xxx.dkr.ecr.ap-northeast-1.amazonaws.com/xxx_web:latest
$ docker push xxx.dkr.ecr.ap-northeast-1.amazonaws.com/xxx_front:latest
```

### ネットワーク作成

1. ECS、RDS用のVPC、サブネット、インターネットゲートウェイを作成
2. ECS、RDS、ロードバランサ用のセキュリティグループを作成
    1. インバウンドルールにHTTPS、HTTP、MySQLを追加しておく、VPC IDは上記で作成したものを追加
3. ロードバランサ作成
※ コンソール画面だと作成できないのでAWSコマンドで作成
上記で作成したサブネットとセキュリティグループを選択
    
    ```bash
    $ aws elbv2 create-load-balancer --name xxx-alb \
        --scheme internet-facing --type application \
        --security-groups sg-xxx \
        --subnets subnet-xxx subnet-xxx \
        --profile xxx
    ```
    

### RDS作成

1. 上記で作成したVPCとセキュリティグループを選択
2. 最初はサーバーレスで最小構成で作成

## ECS対応

ECS用のdocker-compose.ymlを作成

```bash
version: '3'

x-aws-vpc: "vpc-xxx"
x-aws-loadbalancer: "arn:aws:elasticloadbalancing:ap-northeast-1:xxx:loadbalancer/app/xxx-alb/xxx"
  
# ECS用
services:  
  web:
    image: xxx.dkr.ecr.ap-northeast-1.amazonaws.com/xxx_web:latest
    command: >
      bash -c "python manage.py migrate --settings=config.settings.production &&
      python manage.py runserver --settings=config.settings.production 0.0.0.0:8000"
    ports:
      - target: 8000
        x-aws-protocol: http
    x-aws-role:
      Version: '2012-10-17'
      Statement:
        - Effect: Allow
          Action:
            - ssmmessages:CreateControlChannel
            - ssmmessages:CreateDataChannel
            - ssmmessages:OpenControlChannel
            - ssmmessages:OpenDataChannel
          Resource: '*'
  front:
    image: xxx.dkr.ecr.ap-northeast-1.amazonaws.com/xxx_front:latest
    command: yarn start
    ports:
      - "80:80"
      - "443:443"

x-aws-cloudformation:
  Resources:
    WebService:
      Properties:
        LoadBalancers:
          - ContainerName: web
            ContainerPort: 8000
            TargetGroupArn:
              Ref: Web8000TargetGroup
        EnableExecuteCommand: true
    Web8000Listener:
      Properties:
        Port: 8000
        Protocol: HTTP
    Web8000TargetGroup:
      Properties:
        HealthCheckPath: /admin
        Matcher:
          HttpCode: 200-499
    FrontService:
      Properties:
        LoadBalancers:
          - ContainerName: front
            ContainerPort: 80
            TargetGroupArn:
              Ref: FrontTCP80TargetGroup
    FrontTCP80Listener:
      Properties:
        DefaultActions:
          - Type: redirect
            RedirectConfig:
              Port: 443
              Protocol: HTTPS
              StatusCode: HTTP_301
    FrontTCP443Listener:
      Properties:
        Protocol: "HTTPS"
        Certificates:
          - arn:aws:acm:ap-northeast-1:xxx:certificate/xxx-xxx-xxx-xxx-xxx
        DefaultActions:
          - Type: "fixed-response"
            FixedResponseConfig:
              ContentType: "text/plain"
              MessageBody: "BAD DOMAIN NAME"
              StatusCode: "200"
    FrontTCP443WWWRule:
      Type: AWS::ElasticLoadBalancingV2::ListenerRule
      Properties:
        ListenerArn:
          Ref: FrontTCP443Listener
        Priority: 50000
        Conditions:
          - Field: host-header
            HostHeaderConfig:
              Values:
                - xxx.toyscreation.jp
        Actions:
          - Type: redirect
            RedirectConfig:
              Host: xxx.toyscreation.jp
              Port: 443
              Protocol: HTTPS
              StatusCode: HTTP_301
    FrontTCP443BaseRule:
      Type: AWS::ElasticLoadBalancingV2::ListenerRule
      Properties:
        ListenerArn:
          Ref: FrontTCP443Listener
        Priority: 40000
        Conditions:
          - Field: host-header
            HostHeaderConfig:
              Values:
                - xxx.toyscreation.jp
        Actions:
          - Type: forward
            ForwardConfig:
              TargetGroups:
                - TargetGroupArn:
                    Ref: FrontTCP80TargetGroup
```

コンテキスト切替

```bash
$ docker context use xxx
```

起動

```bash
$ docker compose up
```

削除

```bash
$ docker compose down
```

### SSH接続

タスク確認

```bash
$ aws ecs list-tasks --cluster xxx --profile xxx
```

コマンド実行

```bash
$ aws ecs execute-command \
  --cluster xxx \
  --task arn:aws:ecs:ap-northeast-1:xxx:task/xxx/xxx \
  --container web \
  --interactive \
  --command "/bin/sh" \
  --profile xxx
```

※ DRFユーザー作成だけECS ExecでSSHして作成

```bash
$ python manage.py createsuperuser --email xxx@toyscreation.jp --username xxx --settings=config.settings.production
```