1. Webアプリケーションコンテナの作成

目的

Webアプリケーションコンテナを構築します。

対象

  • S3サービス

事前作業

Docker Composeのインストール

コマンド:

which docker-compose

結果(例):

/usr/bin/which: no docker-compose in (/home/ec2-user/.nvm/versions/node/v6.14.4/bin:/usr/local/rvm/gems/ruby-2.4.1/bin:/usr/local/rvm/gems/ruby-2.4.1@global/bin:/usr/local/rvm/rubies/ruby-2.4.1/bin:/usr/local/bin:/bin:/usr/bin:/home/ec2-user/.local/bin:/home/ec2-user/bin:/usr/local/sbin:/usr/sbin:/sbin:/opt/aws/bin:/usr/local/rvm/bin:/home/ec2-user/.local/bin:/home/ec2-user/bin:/home/ec2-user/.local/bin:/home/ec2-user/bin)

変数の設定:

DOCKER_COMPOSE_VERSION='1.22.0'

コマンド:

sudo curl -L \
  "https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" \
  -o /usr/local/bin/docker-compose

結果(例):

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                             Dload  Upload   Total   Spent    Left  Speed
100   617    0   617    0     0   2419      0 --:--:-- --:--:-- --:--:--  2429
100 11.2M  100 11.2M    0     0  1512k      0  0:00:07  0:00:07 --:--:-- 2293k

コマンド:

sudo chmod +x /usr/local/bin/docker-compose

コマンド:

which docker-compose

結果(例):

/usr/local/bin/docker-compose

パラメータ

パラメータの指定

0.1. 開発用ディレクトリの指定

開発用ディレクトリ名を指定します。

変数の設定:

DIR_DEVELOP="${HOME}/environment"

0.2. アプリケーション名の指定

アプリケーション名を指定します。

変数の設定:

APP_NAME='handson-cli-container-flask'

パラメータの確認

変数の確認:

cat << ECHO

  # 0.1. DIR_DEVELOP:"/home/ec2-user/environment"
         DIR_DEVELOP="${DIR_DEVELOP}"
  # 0.2. APP_NAME:"handson-cli-container-flask"
         APP_NAME="${APP_NAME}"

ECHO

実施

変数の設定:

DIR_APP="${HOME}/environment/${APP_NAME}" \
  && echo ${DIR_APP}

結果(例):

/home/ec2-user/environment/handson-cli-container-flask

コマンド:

mkdir -p ${DIR_APP} \
  && cd ${DIR_APP}

アプリケーションコンテナの作成

https://qiita.com/paperlefthand/items/82ab6df4a348f6070a55 のアプリケーションを作成してみます。

コマンド:

mkdir -p app

Flaskアプリケーションの作成

コマンド:
cat << EOF > ${DIR_APP}/app/reply.py
from flask import Flask, jsonify, request
import json
app = Flask(__name__)

@app.route("/", methods=['GET'])
def hello():
    return "Hello World!"

@app.route('/reply', methods=['POST'])
def reply():
    data = json.loads(request.data)
    answer = "Yes, it is %s!\n" % data["keyword"]
    result = {
      "Content-Type": "application/json",
      "Answer":{"Text": answer}
    }
    # return answer
    return jsonify(result)

if __name__ == "__main__":
    app.run(host='0.0.0.0',port=5000,debug=True)
EOF

cat ${DIR_APP}/app/reply.py

結果(例):

from flask import Flask, jsonify, request
import json
app = Flask(__name__)

@app.route("/", methods=['GET'])
def hello():
    return "Hello World!"

@app.route('/reply', methods=['POST'])
def reply():
    data = json.loads(request.data)
    answer = "Yes, it is %s!\n" % data["keyword"]
    result = {
      "Content-Type": "application/json",
      "Answer":{"Text": answer}
    }
    # return answer
    return jsonify(result)

if __name__ == "__main__":
    app.run(host='0.0.0.0',port=5000,debug=True)

アプリケーションコンテナの作成

コマンド:

cat << EOF > ${DIR_APP}/app/Dockerfile
FROM python:3.6

ARG project_dir=/app/

# ADD requirements.txt \$project_dir
ADD reply.py \$project_dir

WORKDIR \$project_dir

RUN pip install flask
# RUN pip install -r requirements.txt

CMD ["python", "reply.py"]
EOF

cat ${DIR_APP}/app/Dockerfile

結果(例):

FROM python:3.6

ARG project_dir=/app/

# ADD requirements.txt $project_dir
ADD reply.py $project_dir

WORKDIR $project_dir

RUN pip install flask
# RUN pip install -r requirements.txt

CMD ["python", "reply.py"]

Dockerイメージの作成

コマンド:

cd ./app/

変数の設定:

CONTAINER_IMAGE_NAME="handson-cli-app"

変数の設定:

CONTAINER_IMAGE_TAG="0.0.1"

コマンド:

docker build -t ${CONTAINER_IMAGE_NAME}:${CONTAINER_IMAGE_TAG} .

結果(例):

Successfully built 190143166695
Successfully tagged handson-cli-app:0.0.1

アプリケーションコンテナの起動

変数の設定:

CONTAINER_RUN_NAME="handson-cli-app"

コマンド:

docker run \
  -p 5000:5000 \
  --name ${CONTAINER_RUN_NAME} \
  ${CONTAINER_IMAGE_NAME}:${CONTAINER_IMAGE_TAG}

アプリケーションの動作確認

別ターミナルでPOSTしてみます。

コマンド:

curl http://localhost:5000/reply \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"keyword": "Keffia"}'

結果(例):

{
  "Answer": {
    "Text": "Yes, it is Keffia!"
  },
  "Content-Type": "application/json"
}

アプリケーションのログ確認

アプリケーションを起動したターミナルでアクセスログを確認します。

ログ(例):

127.0.0.1 - - [16/Sep/2018 05:11:15] "POST /reply HTTP/1.1" 200 -

アプリケーションコンテナの停止

ログを確認したら、CtrlキーとCキーを同時に押して、アプリケーションコンテナを停止します。

コマンド:

docker rm $(docker ps -a --filter name=${CONTAINER_RUN_NAME} -q )

結果(例):

xxxxxxxxxxxx

コマンド:

cd ${DIR_APP}

Webサーバコンテナの作成

コマンド:

mkdir -p nginx

nginx設定ファイルの作成

コマンド:
cat << EOF > ${DIR_APP}/nginx/nginx.conf
events {
  worker_connections 768;
}

http {
  # Nginx will handle gzip compression of responses from the app server
  gzip on;
  gzip_proxied any;
  gzip_types text/plain application/json;
  gzip_min_length 1000;

  server {
    listen 80;

    # Nginx will reject anything not matching /
    location / {
      # Reject requests with unsupported HTTP method
      if (\$request_method !~ ^(GET|POST|HEAD|OPTIONS|PUT|DELETE)\$) {
        return 405;
      }

      # Only requests matching the whitelist expectations will
      # get sent to the application server
      proxy_pass http://app:5000;
      proxy_http_version 1.1;
      proxy_set_header Upgrade \$http_upgrade;
      proxy_set_header Connection 'upgrade';
      proxy_set_header Host \$host;
      proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
      proxy_cache_bypass \$http_upgrade;
    }
  }
}
EOF

cat ${DIR_APP}/nginx/nginx.conf

結果(例):

events {
  worker_connections 768;
}

http {
  # Nginx will handle gzip compression of responses from the app server
  gzip on;
  gzip_proxied any;
  gzip_types text/plain application/json;
  gzip_min_length 1000;

  server {
    listen 80;

    # Nginx will reject anything not matching /
    location / {
      # Reject requests with unsupported HTTP method
      if ($request_method !~ ^(GET|POST|HEAD|OPTIONS|PUT|DELETE)$) {
        return 405;
      }

      # Only requests matching the whitelist expectations will
      # get sent to the application server
      proxy_pass http://app:5000;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection 'upgrade';
      proxy_set_header Host $host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_cache_bypass $http_upgrade;
    }
  }
}

nginxコンテナの作成

コマンド:

cat << EOF > ${DIR_APP}/nginx/Dockerfile
FROM nginx
COPY nginx.conf /etc/nginx/nginx.conf
EOF

cat ${DIR_APP}/nginx/Dockerfile

結果(例):

FROM nginx
COPY nginx.conf /etc/nginx/nginx.conf

Dockerイメージの作成

変数の設定:

CONTAINER_IMAGE_NAME='handson-cli-reverse-proxy'

変数の設定:

CONTAINER_IMAGE_TAG='0.0.1'

コマンド:

docker build -t ${CONTAINER_IMAGE_NAME}:${CONTAINER_IMAGE_TAG} ./nginx/

結果(例):

(略)
Successfully built 35d1eb8be276
Successfully tagged handson-cli-reverse-proxy0.0.1

compose設定ファイルの作成

docker-compose.yml

コマンド:

cat << EOF > ${DIR_APP}/docker-compose.yml
version: '2'
services:
  nginx:
    build:
      context: ./nginx
    ports:
      - 8080:80
    links:
      - app
  app:
    build:
      context: ./app
    ports:
      - 5000:5000
EOF

cat ${DIR_APP}/docker-compose.yml

結果(例):

version: '2'
services:
  nginx:
    build:
      context: ./nginx
    ports:
      - 80:80
    links:
      - app
  app:
    build:
      context: ./app

docker-composeの起動

コマンド:

docker-compose build

結果(例):

(略)
Successfully built xxxxxxxxxxxx
Successfully tagged simple_nginx:latest

コマンド:

docker-compose up

結果(例):

Creating network "simple_default" with the default driver
Creating simple_app_1 ... done
Creating simple_nginx_1 ... done
Attaching to simple_app_1, simple_nginx_1
app_1    |  * Serving Flask app "reply" (lazy loading)
app_1    |  * Environment: production
app_1    |    WARNING: Do not use the development server in a production environment.
app_1    |    Use a production WSGI server instead.
app_1    |  * Debug mode: on
app_1    |  * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
app_1    |  * Restarting with stat
app_1    |  * Debugger is active!
app_1    |  * Debugger PIN: 138-652-460

アプリケーションの動作確認

別ターミナルでPOSTしてみます。

コマンド:

curl http://localhost:8080/reply \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"keyword": "Keffia"}'

結果(例):

{
  "Answer": {
    "Text": "Yes, it is Keffia!"
  },
  "Content-Type": "application/json"
}

アプリケーションのログ確認

アプリケーションを起動したターミナルでアクセスログを確認します。

ログ(例):

nginx_1  | 172.20.0.1 - - [18/Sep/2018:21:47:03 +0000] "POST /reply HTTP/1.1" 200 96 "-" "curl/7.59.0"

アプリケーションコンテナの停止

ログを確認したら、CtrlキーとCキーを同時に押して、アプリケーションコンテナを停止します。

コマンド:

docker rm $(docker ps -a --filter name=handson-cli-container-flask -q )

結果(例):

xxxxxxxxxxxx
xxxxxxxxxxxx

備考