普段あまり Kubernetes をさわる機会はないのですが、たまにさわることがあるといつも忘れていて都度調べなおすことになるので、基本的な部分だけでもメモしておこうと思います。Kubernetes は Docker を使っているので、まずは基礎の基礎からということで今回は Docker の基本的な使い方です。
Kubernetes の基本は2回目の記事をご覧ください。Docker と Kubernetes の基本 2 – Kubernetes
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 には独自に定義されたコマンドを記述していくので、以下によく使うコマンドをメモしておきます。
FROM <image>[:<tag>]
: ベースイメージを設定RUN <command>
: イメージをセットアップするためのコマンド (apt等). 複数記述可能.ENV <key>=<value>
: 環境変数を設定WORKDIR <path>
: Dockerfileのその後のコマンドでワークディレクトリとなるディレクトリを指定.ARG <name>[=<default-value>]
:docker build --build-arg
で指定されるべき引数を定義.ADD <src> <dest>
: ローカルファイルをイメージにコピー. リモートからもコピーできる. 圧縮ファイルは展開される.COPY <src> <dest>
: ローカルファイルをイメージにコピー. リモートからはコピーできない.- ADD と COPY は COPY を使う方が望ましい
ENTRYPOINT <command>
:docker run
実行時にデフォルトで実行されるコマンドCMD <command>
: ENTRYPOINT が無い場合はdocker run
実行時にデフォルトで実行されるコマンド。ENTRYPOINT がある場合は ENTRYPOINT へのデフォルトの引数- 通常 ENTRYPOINT にデフォルトで実行されるコマンドを書き、CMD には ENTRYPOINT への引数を書く。デフォルトのコマンドや引数は、後述の
docker run
で指定されたオプションの方が優先される。
- 通常 ENTRYPOINT にデフォルトで実行されるコマンドを書き、CMD には ENTRYPOINT への引数を書く。デフォルトのコマンドや引数は、後述の
※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 では以下の処理を行うよう定義しています。
FROM
でベースイメージとなるを指定。今回は Python があらかじめインストールされているイメージを使います。- イメージ内にアプリケーション用フォルダ (
/app
)を作成し、ホストにある app.py 等のアプリケーションファイル (現在のディレクトリのファイル) をイメージ内の/app
ディレクトリにコピー。 - ワークディレクトリを移動。
pip install
で python パッケージをインストール。- アプリケーションで使う環境変数を設定。
- コンテナが起動された時に実行されるコマンドを指定 (
python3 app.py
)。
最後に Docker イメージのビルドです。以下のコマンドの -t
オプションは Docker イメージの名前、最後にあるドット “.” は、Dockerfileの場所 (現在のディレクトリ) を示しています。
shell
$ docker build -t my-sample-app .
コンテナの実行
それではコンテナを起動してテストしてみましょう。コンテナを実行するには docker run
コマンドを実行します。今回は以下のオプションを使用します。
--name
: コンテナの名前を指定します。名前を指定すると後述する--link
を指定する時に便利です。--link
: 同じマシンで起動している別のコンテナと通信できるようにします。--link
でコンテナを指定すると、--name
で指定した名前をホスト名としてそのコンテナにアクセスできるようになります。--env
: 環境変数を指定します。このオプションが指定された場合、Dockerfile で書いたENV
の値は上書きされます。
以下のコマンドを実行してテストします。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