普段あまり Kubernetes をさわる機会はないのですが、たまにさわることがあるといつも忘れていて都度調べなおすことになるので、基本的な部分だけでもメモしておこうと思います。Kubernetes は Docker を使っているので、まずは基礎の基礎からということで今回は Docker の基本的な使い方です。

Docker の入門記事を見るとよく WordPress イメージを使ったようなものが出てきますが、それでは自分でアプリを開発する時の参考にならないので、今回は自前で作った簡単な Web アプリを Docker でホストする方法を説明します。

使った OS は Ubuntu 20.04 です。

Docker インストール

shell

# 既にインストールされている Docker があればアンインストール
$ sudo apt-get remove docker docker-engine docker.io containerd runc
$ sudo apt update
$ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
$ sudo apt update
$ sudo apt install docker-ce docker-ce-cli containerd.io

# このままでは docker コマンド実行時に sudo が必要なため、ユーザーを docker グループに追加
$ sudo groupadd docker
$ sudo gpasswd -a <user> docker
$ sudo systemctl restart docker

Dockerfile の基本

Docker ではコンテナイメージをビルドするのに必要な情報を Dockerfile というファイルに記述します。Dockerfile には独自に定義されたコマンドを記述していくので、以下によく使うコマンドをメモしておきます。

※Dockerfile を調べる時にいつも気になって確認して (そしてすぐ忘れて) いたのですが、コンテナのポートとホストのポートのマッピングは Dockerfile では指定しません。docker run コマンドのパラメータ -p <container-port>:<host-port> で指定できます。

今回実行する Web アプリケーション

今回は自前のアプリをコンテナで実行したいので、まずそのアプリケーションを作ります。マイクロサービスアーキテクチャだと複数のサービスが API を介して通信するので、今回も2つのコンテナで通信する構成とします。

とは言っても難しいものを作るのは時間もかかるので、1つの Web アプリにメインページと API の両方を実装します。メインページ用コンテナと API 用コンテナは同じ Docker イメージから起動しますが、2つのコンテナを別々に起動し、メインページ用コンテナが API 用コンテナを呼び出し、値を取得して画面に表示する、という構成です。

以下 Python で実装しました。実行するには pip install flask でFlaskをインストールする必要があります。また使用する環境変数やエンドポイント等の詳細は以下コードにコメントしています。

app.py

import os, requests, json
from flask import Flask, jsonify

app = Flask(__name__)

# コンテナにより変更する必要があるパラメータは環境変数で定義する。
# MY_APP_PORT: アプリケーションが LISTEN するポート
# MY_APP_API_URL: メインページ用のコンテナが API コンテナを呼び出す時に使用する URl。
# MY_APP_NAME: API コンテナが返却する値
MY_APP_PORT = os.getenv("MY_APP_PORT")
MY_APP_API_URL = os.getenv("MY_APP_API_URL")
MY_APP_NAME = os.getenv("MY_APP_NAME")

# メインページ。API用コンテナの /name エンドポイントから取得した値を表示する。
@app.route('/')
def hello():
  res = requests.get(url=MY_APP_API_URL)
  data = json.loads(res.text)
  return "Hello " + data["name"]

# API。環境変数 "MY_APP_NAME" で設定された値を JSON 形式で返却する。
@app.route('/name')
def name():
  content = {"name": MY_APP_NAME}
  return jsonify(content)

if __name__ == "__main__":
  app.run(host="0.0.0.0", port=MY_APP_PORT, debug=False)

なお今回 Python を使っているため、コンテナイメージをビルドする時に依存パッケージをインストールする必要があります。パッケージは pip freeze でテキストファイルに出力できるため、以下のコマンド実行して準備しておきます。

shell

$ cd <app.py のあるディレクトリ>
$ pip freeze > requirements.txt

Docker イメージのビルド

それでは Dockerfile を作成してイメージをビルドします。

shell

$ cd <app.py および requirements.txt のあるディレクトリ>
$ vim Dockerfile

Dockerfile

# syntax=docker/dockerfile:1
FROM python:3.8-slim-buster
RUN mkdir /app
COPY . /app
WORKDIR /app
RUN pip3 install -r requirements.txt
ENV MY_APP_PORT=5001
ENV MY_APP_API_URL=http://localhost:5001/name
ENV MY_APP_NAME=MyName
ENTRYPOINT python3 app.py

上記 Dockerfile では以下の処理を行うよう定義しています。

  1. FROM でベースイメージとなるを指定。今回は Python があらかじめインストールされているイメージを使います。
  2. イメージ内にアプリケーション用フォルダ ( /app )を作成し、ホストにある app.py 等のアプリケーションファイル (現在のディレクトリのファイル) をイメージ内の /app ディレクトリにコピー。
  3. ワークディレクトリを移動。
  4. pip install で python パッケージをインストール。
  5. アプリケーションで使う環境変数を設定。
  6. コンテナが起動された時に実行されるコマンドを指定 (python3 app.py)。

最後に Docker イメージのビルドです。以下のコマンドの -t オプションは Docker イメージの名前、最後にあるドット “.” は、Dockerfileの場所 (現在のディレクトリ) を示しています。

shell

$ docker build -t my-sample-app .

コンテナの実行

それではコンテナを起動してテストしてみましょう。コンテナを実行するには docker run コマンドを実行します。今回は以下のオプションを使用します。

以下のコマンドを実行してテストします。API コンテナとメインページ用コンテナは別々のシェルで起動する必要があるため注意してください。

shell

# シェルを開いて、初めに API 用コンテナを起動
$ docker run --name api --env MY_APP_PORT=5002 --env MY_APP_NAME=MyAPI --env MY_APP_API_URL=NOT_USED my-sample-app
 * Serving Flask app 'app' (lazy loading)
 * ...
 * Running on http://192.168.0.2:5002/ (Press CTRL+C to quit)

# 別のシェルを開いて、メインページ用のコンテナを実行
$ docker run --name host --env MY_APP_PORT=5003 --env MY_APP_API_URL=http://api:5002/name --env MY_APP_NAME=NOT_USED --link api my-sample-app
 * Serving Flask app 'app' (lazy loading)
 * ...
 * Running on http://192.168.0.3:5003/ (Press CTRL+C to quit)

上記メインページ用のコンテナを実行する際、MY_APP_API_URL に指定した URL のホスト名が api になっていて、ポートが 5002 になっているのに注目してください。--link オプションを指定したことで、コンテナ名をホスト名としてアクセスできるようになっています。

メインページ用コンテナを起動した際に表示された URL (上記の例では http://192.168.0.3:5003) にアクセスして画面を確認しましょう。”Hello MyAPI” と表示されたら成功です。

もし表示されなければ、コンテナ上のシェルに入って環境変数などを確認してみましょう。もし成功していれば、コンテナを停止して削除します。それぞれ以下のコマンドで実行できます。

# コンテナ ID を確認
$ docker container ls

# コンテナのシェルに入る
$ docker exec -it <container-id> bash

# コンテナを停止する
$ docker stop <container-id>

# 停止中のコンテナを全て削除する
$ docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y