前回の記事 (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 リソースです。

今回は以下のリソースを作成します。

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 のポート

上記でいくつか注意点があります。

  1. Ingress で作成する L7 Load Balancer はクラウドプロバイダーによって異なります。例えば AWS では Application Load Balancer が作成され使用されます。外部公開される Public IP などもクラウドプロバイダーによって設定が異なるため、使用しているクラウドプロバイダーのドキュメントを確認してください。
  2. 環境変数で定義した “MY_APP_PORT” (アプリケーションが LISTEN するポート) を、Pod で公開するポートおよび Service の転送先ポートに指定していて、メインページ用コンテナ/Pod では 5001、API用コンテナ/Pod では 5002 としています。
  3. メインページ用 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 でアクセスできます。
  4. 上記の 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” と表示されれば成功です。