前回の記事 (Docker と Kubernetes の基本 1 – Docker) では Docker の基本を確認しました。今回は Kubernetes の基本を確認したいと思います。Kubernetes で実行するアプリケーションは前回 Docker で実行したものと同じです。この記事でも手順を説明しますが、詳細は Docker と Kubernetes の基本 1 – Docker を確認してください。
アプリケーションの構成
マイクロサービスのアプリケーションを想定して、メインとなるアプリケーションが 別のREST API を呼び出すような構成にします。ただなるべく簡単にしたいため、メインページと API は同じファイルに書いています。実行する時はメインページ用と API 用のコンテナを別々に起動し、メインページ用コンテナが API 用コンテナを呼び出して結果を表示する、という構成にします。
コードは以下の通りです。コンテナによって変わる部分は環境変数に定義しています。詳細はコメントおよび前回の記事をご確認ください。
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)
また後で必要になるので、requirements.txt を作っておきます。これは pip freeze > requirements.txt
を実行して生成できます。
requirements.txt
certifi==2020.12.5
chardet==4.0.0
click==8.0.0
Flask==2.0.0
idna==2.10
itsdangerous==2.0.0
Jinja2==3.0.0
MarkupSafe==2.0.0
requests==2.25.1
urllib3==1.26.4
Werkzeug==2.0.
Docker イメージの作成
Kubernetes で上記アプリケーションを実行できるよう、アプリケーションを含むコンテナイメージを作成します。まず Dockerfile を app.py および requirements.txt と同じディレクトリに作成します。
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
そして以下のコマンドを実行して Docker イメージを作成します。
shell
$ cd <app.py, requirements.txt, Dockerfile のあるディレクトリ>
$ docker build -t k8s-sample.
これで k8s-sample という名前の Docker イメージができました。次にこのイメージを Kubernetes がダウンロードできる場所に置く必要があります。Kubernetes を AWS 等のパブリッククラウドで使う場合、コンテナレジストリサービスがありますので、そこに置くのが楽だと思います。
例えば AWS では以下のコマンドでイメージを Amazon ECR に登録できるそうです。
shell
# docker コマンドで AWS に認証できるよう設定
$ aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <aws_account_id>.dkr.ecr.<region>.amazonaws.com
# Docker イメージのIDを確認
$ docker tag k8s-sample <aws_account_id>.dkr.ecr.<region>.amazonaws.com/k8s-sample
# Docker イメージを ECR に登録
$ docker push <aws_account_id>.dkr.ecr.<region>.amazonaws.com/k8s-sample
Kubernetes Manifestファイルの作成
Kubernetes で設定する内容は Manifest ファイルと呼ばれる yaml ファイルに記述します。以下よく使われる Kubernetes リソースです。
Ingress
: Kubernetes クラスタの入り口になる L7 Load Balancer。リクエストをバックエンドとなるService
に渡します。Service
: Pod のまとまりに対して負荷分散を行う L4 Load Balancer。バックエンドとなるPod
にリクエストを渡します。Service
には以下のタイプがあります。ClusterIP
: デフォルトの Service タイプ。クラスタ内にのみ Pod を公開する。NodePort
: ノードのポートが固定され、クラスタ外からのアクセスを Pod に渡せる。LoadBalancer
: L4 Load Balancer としてクラスタの入り口となり外部にサービスを公開します。Ingress との違いは L7 か L4 かという点です。例えば Ingress は L7 なので SSL を終端できますが、LoadBalancer Service はできません。
Deployment
: Pod のまとまりを定義し、Pod のレプリカ数に加えてコンテナのバージョンアップ時の動作等を設定できます。Pod
: コンテナのまとまりです (今回の例では1つの Pod は1つのコンテナしか含みません)。Kubernetes リソースの1つですが、通常は Pod のみを yaml ファイルに書くことは無く、上位のまとまりであるDeployment
を使います。
Horizontal Pod Autoscaler (HPA)
:Pod
のオートスケーリング方法を定義します。今回の記事では省略しましたが、通常はよく使用されます。
今回は以下のリソースを作成します。
Ingress
: 1つ。クラスタを外部に公開するために使用。Deployment
: 2つ。メインページ用コンテナを含む Pod 用に1つ、API 用コンテナを含む Pod 用に1つ。Service
: 2つ。2つの Deployment それぞれに作成。
yaml ファイルの分け方は色々議論があるようですが、今回は Ingress と Service の単位で3つのファイルに定義します。
my-api-service.yaml (API用 Service および Pod の定義)
apiVersion: v1
kind: Service
metadata:
name: k8s-sample-api-service
namespace: k8s-sample
labels:
app: k8s-sample-api-service
spec:
type: ClusterIP
ports:
- port: 80 # Service が公開するポート
targetPort: 5002 # リクエスト転送先 Pod のポート
protocol: TCP
selector:
app: k8s-sample-api-pod # この Label を持つ Pod にリクエストを転送
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: k8s-sample-api-deployment
namespace: k8s-sample
labels:
app: k8s-sample-api-deployment
spec:
replicas: 3
selector:
matchLabels:
app: k8s-sample-api-pod # この Label を持つ Pod にレプリカ数を適用
template: # ここから以下 Pod の定義
metadata:
labels:
app: k8s-sample-api-pod # Pod の Label 定義。わかりやすければなんでも良い
spec:
containers:
- name: k8s-sample-api-container
ports: # Pod が公開するポート
- name: http
containerPort: 5002
protocol: TCP
resources: # Pod が使用できる CPU/メモリの定義
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 256Mi
image: <aws_account_id>.dkr.ecr.<region>.amazonaws.com/k8s-sample # 上記で AWS ECR に配置した Docker イメージ
env: # コンテナ内に設定する環境変数。今回のアプリケーションに合わせて設定
- name: MY_APP_PORT
value: "5002"
- name: MY_APP_API_URL
value: "NOT_USED"
- name: MY_APP_NAME
value: "MyAPIService"
livenessProbe: # コンテナの死活監視の定義
httpGet:
path: /name
port: 5002
initialDelaySeconds: 10
periodSeconds: 3
readinessProbe: # コンテナの状態監視の定義
httpGet:
path: /name
port: 5002
initialDelaySeconds: 10
periodSeconds: 3
my-website-service.yaml (メインページ用 Service および Pod の定義)
apiVersion: v1
kind: Service
metadata:
name: k8s-sample-website-service
namespace: k8s-sample
labels:
app: k8s-sample-website-service
spec:
type: NodePort
ports:
- port: 80 # Service が公開するポート
targetPort: 5001 # リクエスト転送先 Pod のポート
nodePort: 30001 # ノードのポート。アプリケーション上では意識する必要がない
protocol: TCP
selector:
app: k8s-sample-website-pod # この Label を持つ Pod にリクエストを転送
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: k8s-sample-website-deployment
namespace: k8s-sample
labels:
app: k8s-sample-website-deployment
spec:
replicas: 3
selector:
matchLabels:
app: k8s-sample-website-pod # この Label を持つ Pod にレプリカ数を適用
template: # ここから以下 Pod の定義
metadata:
labels:
app: k8s-sample-website-pod # Pod の Label 定義。わかりやすければなんでも良い
spec:
containers:
- name: k8s-sample-website-container
ports: # Pod が公開するポート
- name: http
containerPort: 5001
protocol: TCP
resources: # Pod が使用できる CPU/メモリの定義
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 256Mi
image: <aws_account_id>.dkr.ecr.<region>.amazonaws.com/k8s-sample # 上記で AWS ECR に配置した Docker イメージ
env: # コンテナ内に設定する環境変数。今回のアプリケーションに合わせて設定
- name: MY_APP_PORT
value: "5001"
- name: MY_APP_API_URL
value: "http://k8s-sample-api-service:80/name"
- name: MY_APP_NAME
value: "NOT_USED"
livenessProbe: # コンテナの死活監視の定義
httpGet:
path: /name
port: 5001
initialDelaySeconds: 10
periodSeconds: 3
readinessProbe: # コンテナの状態監視の定義
httpGet:
path: /name
port: 5001
initialDelaySeconds: 10
periodSeconds: 3
my-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: k8s-sample-ingress
namespace: k8s-sample
annotations: # この部分はクラウドプロバイダーによって異なります。以下 AWS の例。
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
spec:
rules:
- http:
paths:
- path: /*
backend:
serviceName: k8s-sample-website-service # リクエスト転送先の Service
servicePort: 80 # 転送先 Service のポート
上記でいくつか注意点があります。
- Ingress で作成する L7 Load Balancer はクラウドプロバイダーによって異なります。例えば AWS では Application Load Balancer が作成され使用されます。外部公開される Public IP などもクラウドプロバイダーによって設定が異なるため、使用しているクラウドプロバイダーのドキュメントを確認してください。
- 環境変数で定義した “MY_APP_PORT” (アプリケーションが LISTEN するポート) を、Pod で公開するポートおよび Service の転送先ポートに指定していて、メインページ用コンテナ/Pod では 5001、API用コンテナ/Pod では 5002 としています。
- メインページ用 Pod (my-website-service.yaml に定義) で、API 用 Pod の URL を “http://k8s-sample-api-service:80/name” としています。これは同じ Namespace 内であれば、Service 名をホスト名としてその Service にアクセスできるためです。もし同じ Namespace に無ければ、
http://<service-name>.<namespace>.svc.cluster.local:<service-port>
のような形式の URL でアクセスできます。 - 上記の yaml 内でそれぞれのリソースに
namespace: k8s-sample
と定義しています。”Namespace” は Kubernetes クラスタ内でアプリケーションを分割する仕組みで、通常はサービスやアプリケーション毎に Namespace を分けます。今回は1つのアプリケーションのため、全てのリソースを同じ “k8s-sample” Namespace に含めています。
それでは Kubernetes クラスタにリソースを作成します。クラスタは AWS 等のパブリッククラウドに作る方法や、自前のクラスタを構築する方法がありますが、範囲が広いので今回は割愛します。クラスタが作成できたら、kubectl
コマンドでそのクラスタを操作できるよう認証設定を行う必要があります。例えば AWS EKS では以下のコマンドを実行します。
shell
$ aws eks --region <region> update-kubeconfig --name <cluster-name>
# 確認のため以下を実行。エラーが表示されなければOK。
$ kubectl get all
問題なければ、まず kubectl
コマンドで Namespace を作成します。ついでにデフォルトで操作する Namespace として設定しておくと、後のコマンドで都度 Namespace を指定する手間が省けて楽になります。
shell
# Namespace を作成
$ kubectl create namespace k8s-sample
# デフォルトの Namespace を設定
$ kubectl config set-context --current --namespace="k8s-sample"
最後に上記作成した Manifest ファイルを順に適用します。
shell
$ cd <Manifest ファイルを置いたディレクトリ>
$ kubectl apply -f ./my-api-service.yaml
$ kubectl apply -f ./my-website-service.yaml
$ kubectl apply -f ./my-ingress.yaml
Ingress に割り当てられた Public IP にアクセスし、”Hellow MyAPIService” と表示されれば成功です。