【とりあえずハンズオン】DockerでDjangoを動かすだけの話 - 準備編

# はじめに

この記事はDocker で Python の Web フレームワーク、Django を動かすだけのハンズオンです。
ベストプラクティスや間違いがあれば、書き直していく予定です。

## 環境

- ヤマダにペペロンチーノをこぼされた初代 Surface Book

  - 実装 RAM 8GB

- エディション Windows 10 Pro
- バージョン 20H2
- OS ビルド 19042.867

- Chrome

  - 89.0.4389.90（Official Build） （64 ビット）

## Docker とは

ざっくり、コンテナと呼ばれる単位で環境を構築・管理することができるツール
OS レベルの仮想化によって動作して
アプリケーションとその動作環境をコンテナという単位で保存しておくことができる。

どこが凄いかと言えば、アプリケーションの環境をコンテナという単位で可搬することが可能なところ。

### Docker 以前

Docker 以前は

> アプリケーションに使われるライブラリやその他パッケージをドキュメント等に残し
> 同じ環境を作る操作を環境ごとにやる必要があった。
>
> 同じ環境を作る操作を誤ったり、間違えたりするとキチンと動作しなかったり
> 想定とは違う動作になったりする為
> 同じ環境を作る操作以外にも環境構築の不具合に悩まされ、時間を取られるということがよくあった。

そこで一度作った環境をコンテナという単位でパッケージ化して
他の端末やサーバで扱えるようにしようとできたのが Docker である。

### Docker 以後

どんなことが可能になったかをざっくり言うと
まずは開発環境の標準化が容易になり、環境セットアップ資料というモノから解放された(極端なことを言うと)

開発環境を他の人と共有するという概念が生まれた。
※以前にも仮想マシンという形で環境を共有することは可能でしたが Docker ほど簡単ではなかった。

環境のローリングアップデートがコンテナオーケストレーションによって簡単にできるようになった。
※とはいえ、Docker 技術にキャッチアップするのは割と難しい。現役のエンジニアでもそこそこ難しいところ。

## ハンズオンセットアップ

ざっくり、流れを説明

1. AWS 上に EC2 を構築
2. Visual Studio Code で Remote SSH をインストール
3. Remote SSH の設定
4. EC2 に Docker をインストール
5. EC2 に Docker-Compose をインストール
6. Python3 のセットアップ
7. pip3 で Django をインストール
8. Django ローカルホストで実行

## AWS 上に EC2 を構築

まずはコンソールを開いて。。。
と以前から CloudFormation の記事で EC2 の構築をやっているので
今回は IaC で構築します。

### VPC を構築

まずは EC2 を置くためのサブネットを切る為に VPC を
CloudFormation で構築します。

CloudFormation の管理画面を開きます。
スタックの作成をクリック

![cfn_stack.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/6d00afea-2bce-a422-0695-79ce7c8f9ad0.jpeg)

```yml:vpc_network.yml

AWSTemplateFormatVersion: 2010-09-09
Parameters:
  VpcCidrBlock:
    Description: VpcCidrBlock
    Type: String
    Default: 10.0.0.0/21

Resources:
  MainVpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCidrBlock
      EnableDnsHostnames: true
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: MainVpcfromCF
  IGW:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: igw
          Value: igw-cf
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref MainVpc
      InternetGatewayId: !Ref IGW

Outputs:
  MainVpc:
    Value: !Ref MainVpc
    Export:
      Name: MainVpcId
  MainIgw:
    Value: !Ref IGW
    Export:
      Name: MainIGWId
```

ファイル選択から vpc_network.yml をアップロードします。

![vpc_network.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/9c3772cb-e0a8-3827-34ec-8e00834f4431.jpeg)

スタック名は「vpc_network」とします。

![vpc_network-2.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/b3a3fc55-d592-4dde-9653-3a28299a399a.jpeg)

スタックの作成には時間がかかりますので少し待ちましょう。
スタックの完成予想図。

![vpc_network-3.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/a13286f0-45bc-6495-710b-45d2d7b91815.jpeg)

### サブネットを切る

VPC が構築できたらサブネットを切ります。
今回は django アプリケーション公開用にセキュリティグループに TCP でポート 8000 を開けます。

```yml:DjangoSubnet.yml
AWSTemplateFormatVersion: 2010-09-09

Parameters:
  AZ:
    Description: AvailabilityZone
    Type: String
    Default: "ap-northeast-1a"
  PublicSubnetCidrBlock:
    Description: PublicSubnetCidrBlock
    Type: String
    Default: 10.0.2.0/24

Resources:
  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Ref AZ
      VpcId: !ImportValue MainVpcId
      CidrBlock: !Ref PublicSubnetCidrBlock
      Tags:
        - Key: Name
          Value: PublicSubnet
  PubRT:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !ImportValue MainVpcId
      Tags:
        - Key: PublicRoute
          Value: PublicRouteCf
  PubToInternet:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PubRT
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !ImportValue MainIGWId
  PubRTAssociate:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet
      RouteTableId: !Ref PubRT
  secSSHGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: PublicSecGrp
      GroupDescription: PublicSecGrp
      VpcId: !ImportValue MainVpcId
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 8000
          ToPort: 8000
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: PublicSecGrp

Outputs:
  PublicSubnet:
    Value: !Ref PublicSubnet
    Export:
      Name: PublicSubnet
  PublicSecGrp:
    Value: !GetAtt secSSHGroup.GroupId
    Export:
      Name: PublicSecGrp

```

ファイル選択から DjangoSubnet.yml をアップロードしてスタックを作成
![DjangoSubnet.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/1df71470-53c2-e114-e333-81a40ada0562.jpeg)

スタック名を「django-subnet」とします。
![django-subnet.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/6e545da4-325d-e2b1-0985-44637284fdb3.jpeg)


スタックの作成には時間がかかりますので少し待ちましょう。
スタックの完成予想図。

![cloud_formation_complete.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/a91d997c-3d01-812c-5988-530e109e0900.jpeg)

### キーペアを作成する

AWS コンソールから EC2 を開き、左ペインからキーペアを選択

![ec2_keypair.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/02e0d74b-2e77-d55a-89f5-bba6d6211806.jpeg)

キーペアを作成をクリック

![create_key_pair.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/9a486c9b-3c3e-038b-3e2f-4ab266c0dab9.jpeg)

キーペアを「Mykeypair」という名前で作成

![Mykeypair.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/f02f5097-523d-fef8-f0ef-f0466cd7c644.jpeg)

### EC2 を構築する

CloudFormation の画面に戻り、EC2 を作成する。

```yml:public_django_ec2.yml
AWSTemplateFormatVersion: 2010-09-09
Resources:
  myEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      KeyName: Mykeypair
      ImageId: ami-0992fc94ca0f1415a
      InstanceType: t2.micro
      Monitoring: false
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          DeviceIndex: "0"
          GroupSet:
            - !ImportValue PublicSecGrp
          PrivateIpAddress: "10.0.2.10"
          SubnetId: !ImportValue PublicSubnet
      Tags:
        - Key: Name
          Value: PublicEC2

```

ファイル選択から public_django_ec2.yml をアップロード

![cfn_public_django_ec2.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/d697fa56-a73d-9192-110c-304d504a5413.jpeg)

### スタックの完成予想図

![完成予想図.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/d81ab724-78be-97e6-30cf-50989aae92d7.jpeg)

### セキュリティグループの確認

SSH 用のポートと公開用のポート番号が開かれていることを確認しましょう。

![public_sec_grp.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/97c76e77-972f-f985-eca7-41d26e251268.jpeg)

## Visual Studio Code の設定

### Visual Studio Code で Remote SSH をインストール

EC2 に SSH でログインする為に
VisualStudioCode のプラグインである「Remote SSH」をインストールします。

まだ、インストールをしていない人は以下のようにインストールと表示されていると思います。

![remote_ssh.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/60ed11d6-0554-4878-343f-cd127bfb6877.jpeg)

インストールされている方は以下

![remote_ssh_OK.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/2a68c729-48f4-5edb-d0b7-cd504b23cebe.jpeg)

### Remote SSH の設定

Visual Studio Code の拡張機能 Remote SSH を設定します。

まず、Visual Studio Code の画面左下の緑ボタンを押します。
![green_button.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/f496502c-70a9-1cad-e783-fd438272c0d4.jpeg)

するとウィンドウの上部に選択肢が出るので「Connect to Host...」を選択

![remote_ssh_select.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/809ea23d-c91c-7d11-dcab-aacf673889c8.jpeg)

続いて「Configure SSH Hosts」を選択

![remote_ssh_select-2.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/8315ee12-d7e8-25cd-aa48-eab9889680d9.jpeg)

Remote SSH の格納先を問われるのでユーザ名配下の.ssh を選択

![remote_ssh_select-3.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/0d9fd0f1-5d85-7810-4753-63409931974f.jpeg)

以下の内容を環境に合わせて記載。

```txt

Host Django-EC2
  HostName {パブリックIPアドレス}
  IdentityFile {キーペア名(フルパス)}
  User ec2-user

```

コンフィグを保存して閉じる。
再度、緑のボタンを押して Remote SSH 接続を開始すると新しいウィンドウが開かれる。

![remote_ssh_select-4.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/88362960-63c1-75e8-7b82-b28f5f845127.jpeg)

### Django を試す

### EC2 Django のセットアップ

「Open Folder 」を選択
![open_folder-1.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/c79312dd-c6df-7257-d2df-e969e5763cc0.jpeg)

ディレクトリを選択
ec2-user のホームディレクトリ「/home/ec2-user/」を選択

![open_folder-2.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/e900447e-5e49-224f-c396-da4f62283832.jpeg)

Visual Studio Code のウィンドウ左ペインから
ファイルを新規作成

以下のような Bash スクリプトを書いて EC2 の「/home/ec2-user/」に保存する。

``` bash:django-install.sh

# !/bin/bash

sudo yum update -y && \
sudo amazon-linux-extras install  -y docker && \
sudo usermod -a -G docker ec2-user && \
sudo systemctl start docker.service && \
sudo systemctl status docker.service && \
sudo systemctl enable docker.service && \
sudo curl -L https://github.com/docker/compose/releases/download/1.21.0/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose && \
sudo chmod +x /usr/local/bin/docker-compose && \
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose && \
sudo yum install -y python37 python3-devel gcc jq tree git && \
git clone https://github.com/ymd65536/django_test.git && \
cd django_test/source && \
cd src && \
pip3 install -r requirements.txt --user && \
python3 manage.py migrate && \
python3 manage.py runserver 0.0.0.0:8000

```

&(アンパサンド)を使うことでコマンドの実行がこけた時の対策ができる。
例えば、今回のケースだと最初の yum がこけた場合は以降のコマンドは実行されない。
また、バックスラッシュでコマンド同士をつなぐとコマンド同士をつないでから実行するという意味になる。

作成した Bash スクリプトを実行する。

```bash
[ec2-user@ip-10-0-2-10 ~]$ pwd
/home/ec2-user
[ec2-user@ip-10-0-2-10 ~]$
[ec2-user@ip-10-0-2-10 ~]$ ls
django-install.sh
[ec2-user@ip-10-0-2-10 ~]$
[ec2-user@ip-10-0-2-10 ~]$  bash ./django-install
-------------------------------------
Performing system checks...

System check identified no issues (0 silenced).
April 10, 2021 - 13:30:12
Django version 2.0, using settings 'todobackend.settings'
Starting development server at http://0.0.0.0:8000/
Quit the server with CONTROL-C.

```

### HTTP で開く

EC2 上に Django によって作成された Web ページがオープンされたので
http://パブリック IP アドレス:8000 でアクセスする。

うまくいくとこんな感じに Django の REST Framework が表示される。

![django-http.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/66e32d95-8a9e-0b8b-2e60-b862a2feb5f2.jpeg)


### REST API で遊んでみる

http://localhost:8000/todos を開く

UI で操作するのも良いですがここでは curl で操作してみることにする。

```bash

#POSTでitemを作成する
curl -X POST -H "Content-Type: application/json" localhost:8000/todos -d '{"title": "推しのリプライを確認", "order": 1}'

```

※途中動かなくなったと思ってガチャガチャとリクエストを打ったら todo 番号がめちゃくちゃになりました。

![oshirep-1.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/636dfd36-2a43-0ad2-98e2-9d0c0d84fb3c.jpeg)


```bash

#タスクを更新する（localhost:8000/todos/16 のアイテムのcompletedを更新）
curl -X PATCH -H "Content-Type: application/json" localhost:8000/todos/16  -d '{"completed":"true"}'

```

![oshirep-2.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/893cdb8e-cce1-5a2c-e942-da3057e6308d.jpeg)

「推しのリプライを確認」16 の todo ステータスが true になりましたね。
他の 14 と 15 の todo を消してみましょう。

```bash

#タスクを削除する（localhost:8000/todos/14,localhost:8000/todos/15 のアイテムを削除）
curl -X DELETE  -H "Content-Type: application/json" localhost:8000/todos/14
curl -X DELETE  -H "Content-Type: application/json" localhost:8000/todos/15

```

![oshirep-3.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/2414622c-6ee0-70cd-a3e9-1fefd0acf4a7.jpeg)


todo 番号 14 と 15 が消えましたね。

### todos の中身を見る

```
[ec2-user@ip-10-0-2-10 src]$ curl http://localhost:8000/todos | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   110  100   110    0     0   5789      0 --:--:-- --:--:-- --:--:--  5789
[
  {
    "url": "http://localhost:8000/todos/16",
    "title": "推しのリプライを確認",
    "completed": true,
    "order": 1
  }
]
[ec2-user@ip-10-0-2-10 src]$
```

## テストプログラムを実行

あらかじめ用意しておいたテストプログラムを実行する。
まずは、テスト用プログラムに利用するパッケージをインストールする為に
ディレクトリを移動する。

```terminal

[ec2-user@ip-10-0-2-10 src]$ cd /home/ec2-user/django_test/source/src/
[ec2-user@ip-10-0-2-10 src]$ pip3 install -r requirements_test.txt --user
[ec2-user@ip-10-0-2-10 src]$ python3 manage.py test --settings todobackend.settings_test

----------------------------------------------------------------------
XML: /home/ec2-user/django_test/source/src/unittests.xml
Name                              Stmts   Miss  Cover
-----------------------------------------------------
todo/__init__.py                      0      0   100%
todo/admin.py                         1      1     0%
todo/migrations/0001_initial.py       5      0   100%
todo/migrations/__init__.py           0      0   100%
todo/models.py                        6      6     0%
todo/serializers.py                   7      0   100%
todo/urls.py                          6      0   100%
todo/views.py                        17      0   100%
-----------------------------------------------------
TOTAL                                42      7    83%
----------------------------------------------------------------------
Ran 12 tests in 0.181s

OK

Destroying test database for alias 'default'...
[ec2-user@ip-10-0-2-10 src]$

```

rails test もテストにパスすると最後に OK が出るのでなんだか親しみやすいですね。

## まとめ

今回は CloudFormation で VPC と EC2 を構築して django 環境を建て
Django REST Framework で遊んでみました。
次回は EC2 インスタンス上で Docker を扱っていきます。

## おわり