とことんDevOps | 日本仮想化技術のDevOps技術情報メディア

DevOpsに関連する技術情報を幅広く提供していきます。

日本仮想化技術がお届けする「とことんDevOps」では、DevOpsに関する技術情報や、日々のDevOps業務の中での検証結果、TipsなどDevOpsのお役立ち情報をお届けします。
主なテーマ: DevOps、CI/CD、コンテナ開発、IaCなど
読者登録と各種SNSのフォローもよろしくお願いいたします。

kindでKubernetesクラスターを動かす

今回はDockerの中でKubernetesクラスターを簡単に作成できる「kind」を使ってみたいと思います。 kindは、Dockerコンテナの「ノード」を使用してローカルのKubernetesクラスタを実行するためのツールです。 kindは元々、主にKubernetes自体をテストするために設計されましたが、ローカル開発またはCI/CDに使用されることもあります。

早速動かしてみます。

インストールの前に必要なもの

実際、kindをインストールする前に必要なものはDockerが動く環境です。ちょっと触ってみたいだけなら、手持ちのクライアントにDocker DesktopかRancher Desktopを入れておきます。

自由に使えるマシンがあるなら、LinuxをインストールしてDockerを入れておきます。今回の内容のレベルであれば、Linuxデスクトップ環境は必須ではありません。

インストール

macOSの場合はHomebrewやMacPorts、Windowsの場合はChocolateyを使ったkindのインストール例が載っています。今回はmacOSの場合の例を示します。

% brew install kind

他のOS向けのセットアップ方法については、QuickStartガイドに書かれています。

kind.sigs.k8s.io

これでインストールされます。 次にクラスターの作成はこんな感じです。名前を指定しなかった場合は、デフォルトの名前でクラスターが作られます。

% kind create cluster --name mykind
Creating cluster "mykind" ...
 ✓ Ensuring node image (kindest/node:v1.23.4) 
 ✓ Preparing nodes 📦  
 ✓ Writing configuration 📜 
 ✓ Starting control-plane 🕹️ 
 ✓ Installing CNI 🔌 
 ✓ Installing StorageClass 💾 
Set kubectl context to "kind-mykind"
You can now use your cluster with:

kubectl cluster-info --context kind-mykind

Not sure what to do next? 😅  Check out https://kind.sigs.k8s.io/docs/user/quick-start/

% kind get clusters
mykind

コンテナアプリを動かしてみよう

ではいつものマニフェストファイルを使って、NGINX Podを作ってみましょう。サーバーにアクセスできるようにサービスも作っておきます。

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginxapp1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginxapp1
  template:
    metadata:
      labels:
        app: nginxapp1
    spec:
      containers:
      - name: nginxapp1
        image: docker.io/library/nginx:alpine
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginxapp1-nodeport
  labels:
    app: nginxapp1
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30080
  selector:
    app: nginxapp1

Podとサービスを作成します。

% kubectl apply -f nginx-deployment.yaml
deployment.apps/nginxapp1 created
service/nginxapp1-nodeport created

問題なく作成できました。

% kubectl get -f nginx-deployment.yaml
NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginxapp1   0/1     1            0           5s

NAME                         TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
service/nginxapp1-nodeport   NodePort   10.96.108.158   <none>        80:30080/TCP   5s

type: NodePortを使ってサービス定義していますので、ブラウザーでアクセスしてみましょう。すると、次のようにアクセスできません。

% curl http://192.168.0.6:30080
curl: (7) Failed to connect to 192.168.0.6 port 30080 after 13 ms: Connection refused

kindはminikubeとはちょっと異なり、全てのサービスがDockerコンテナ内で動作します。そのため、事前に必要なポートをマッピングする形で用意する必要があります。では具体的にどう設定するかですが、kindの「Extra Port Mappings」を使った方法をとります。

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30080
    hostPort: 30080

NodePortが使える様に作り直す

一度クラスターを削除して、設定ファイルを指定してクラスターを作り直してみます。

% kind delete cluster --name mykind
Deleting cluster "mykind" ...

% kind create cluster --name mykind --config=config.yaml

もう一度試してみます。アクセスできるようになるまで少々時間がかかります。

% kubectl apply -f nginx-deployment.yaml
% curl http://192.168.0.6:30080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

Portがいくつか必要であれば、列挙しておけば良いようです。

---
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30080
    hostPort: 30080
  - containerPort: 30443
    hostPort: 30443

クラスターバージョンの指定についても、設定ファイルに書くことで可能になっています。kindはKubernetesのバージョンについてはKindのバージョンに設定されたデフォルトバージョンが利用されます。通常はそれでも良いですが、バージョンを指定しておきたい時もあると思います。

その場合にはまず、現在使っているkindのバージョンを確認します。

% kind --version
kind version 0.12.0

そのリリースページを確認します。対応しているバージョンが書かれているので、あとはそれを次のように書くだけです。

例えばKubernetes 1.22.7を使いたい場合は、次のように指定できます。バージョン指定する場合に注意する点として、そのバージョンのkindで対応していイメージを指定しないとクラスターが正常に動かない場合があるという点と、バージョンを指定しなかった場合はおおよそ最新のバージョンでクラスタ作成されるという点です。

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  image: kindest/node:v1.22.7@sha256:1dfd72d193bf7da64765fd2f2898f78663b9ba366c2aa74be1fd7498a1873166
  extraPortMappings:
  - containerPort: 30080
    hostPort: 30080
  - containerPort: 30443
    hostPort: 30443
  - containerPort: 30950
    hostPort: 30950

コンテナアプリケーションの開発にDevOps CI/CDの概念を取り込んだ場合に、開発したコンテナアプリケーションの実行をテストしたい場合、kindを使うと便利かもしれません。 CI/CDのなかでKubernetesクラスターを作成してコンテナアプリケーションが動くか試すといった場合には、バージョン指定をしておくと良いと思います。

クラスターのバージョンを確認すると、1.22.7になっていることが確認できます。

% kubectl get no
NAME                   STATUS   ROLES                  AGE     VERSION
mykind-control-plane   Ready    control-plane,master   7m57s   v1.22.7

ローカルイメージレジストリーを構築しよう

CI処理では、開発しているアプリケーションが安定稼働するまで何度も何度もCI/CDのサイクルが回ることになります。コンテナーをアプリケーションのプラットフォームとして使う場合、コンテナーイメージとイメージレジストリーが重要になってきます。

コンテナーイメージはイメージレジストリーからイメージをダウンロードしてランタイムエンジン(例えばDockerやcontainerd、CRI-O)にキャッシュしてクラスター内で使われます。イメージのダウンロード元はランタイムの設定で定義されていますが、多くの場合はDocker Hubのレジストリーからイメージをダウンロードします。

Docker Hub以外にもコンテナイメージを配信しているレジストリーはいろいろ存在しますが、多くのレジストリーではイメージのPull制限があることが一般的です。Docker Hubも例外ではなく、Dockerのライセンスやログインの有無によって若干変わってきます。

そしてPull制限に引っかかってしまうと具体的にどんな問題が起こるかというとイメージのPull、つまりダウンロードができないので、CIの処理がうまく回らなくなります。

そこで、CIで利用するイメージをローカルのコンテナレジストリーを構築して、そこでイメージの管理、利用をしようということになります。

kindはCI/CDなどでも使うことを想定して、それを実現する方法が用意されてます。

kind.sigs.k8s.io

この情報をベースに、NodePortも使える構成でクラスターを作ってみましょう。次の様なシェルスクリプトを用意します。

#!/bin/sh
set -o errexit

# create registry container unless it already exists
reg_name='kind-registry'
reg_port='5001'
if [ "$(docker inspect -f '{{.State.Running}}' "${reg_name}" 2>/dev/null || true)" != 'true' ]; then
  docker run \
    -d --restart=always -p "127.0.0.1:${reg_port}:5000" --name "${reg_name}" \
    registry:2
fi

# create a cluster with the local registry enabled in containerd
# ちょっとだけ追記
cat <<EOF | kind create cluster --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30080
    hostPort: 30080
  - containerPort: 30443
    hostPort: 30443
containerdConfigPatches:
- |-
  [plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:${reg_port}"]
    endpoint = ["http://${reg_name}:5000"]
EOF

# connect the registry to the cluster network if not already connected
if [ "$(docker inspect -f='{{json .NetworkSettings.Networks.kind}}' "${reg_name}")" = 'null' ]; then
  docker network connect "kind" "${reg_name}"
fi

# Document the local registry
# https://github.com/kubernetes/enhancements/tree/master/keps/sig-cluster-lifecycle/generic/1755-communicating-a-local-registry
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: local-registry-hosting
  namespace: kube-public
data:
  localRegistryHosting.v1: |
    host: "localhost:${reg_port}"
    help: "https://kind.sigs.k8s.io/docs/user/local-registry/"
EOF

実行してみます。

% ./kind-with-registry.sh 
Unable to find image 'registry:2' locally
2: Pulling from library/registry
a5e44472bb1f: Pull complete 
39b0b4c4587f: Pull complete 
bd7c1c2b0748: Pull complete 
c82d7ec052b5: Pull complete 
a372bb0b4117: Pull complete 
Digest: sha256:9f2f33b11e13618ce2f7c6e4bd70fc479aac816116eb5a590edd4176065f0471
Status: Downloaded newer image for registry:2
b99c5c2d705eab0685fd4d16b6f5311f019c5156ca31f67603ef1eea417df874
Creating cluster "kind" ...
 ✓ Ensuring node image (kindest/node:v1.23.4) 🖼 
 ✓ Preparing nodes 📦  
 ✓ Writing configuration 📜 
 ✓ Starting control-plane 🕹️ 
 ✓ Installing CNI 🔌 
 ✓ Installing StorageClass 💾 
Set kubectl context to "kind-kind"
You can now use your cluster with:

kubectl cluster-info --context kind-kind

Not sure what to do next? 😅  Check out https://kind.sigs.k8s.io/docs/user/quick-start/
configmap/local-registry-hosting created

うまく動いたようですね。

ローカルイメージレジストリーを使ってみる

% docker pull gcr.io/google-samples/hello-app:1.0
1.0: Pulling from google-samples/hello-app
59bf1c3509f3: Pull complete 
da75187f77d8: Pull complete 
Digest: sha256:88b205d7995332e10e836514fbfd59ecaf8976fc15060cd66e85cdcebe7fb356
Status: Downloaded newer image for gcr.io/google-samples/hello-app:1.0
gcr.io/google-samples/hello-app:1.0

% docker tag gcr.io/google-samples/hello-app:1.0 localhost:5001/hello-app:1.0

% docker push localhost:5001/hello-app:1.0
The push refers to repository [localhost:5001/hello-app]
6aebdb5560a6: Pushed 
8d3ac3489996: Pushed 
1.0: digest: sha256:88b205d7995332e10e836514fbfd59ecaf8976fc15060cd66e85cdcebe7fb356 size: 739

上手く動いているようです。 それでは、この格納したイメージを使って、Podを作ってみます。

% kubectl create deployment hello-server --image=localhost:5001/hello-app:1.0
deployment.apps/hello-server created
% kubectl get deployment
NAME           READY   UP-TO-DATE   AVAILABLE   AGE
hello-server   1/1     1            1           26s

問題なく動作するのを確認できました。

kindによって構築できるKubernetesクラスターは厳密には最終的にアプリケーションを動かすクラウドのKubernretsとは全く同一ではありません。従って最終的には本番環境のKubernetesクラスターで動作チェックすることにはなると思うのですが、初期の動作検証にはkindを利用したKubernetesクラスターをテスト用に使うのは良いと思います。

今回は省略しましたが、環境さえ整えばIngressやLoadbalancerも利用でき、よりプロダクション環境に近付くと思います。まだkindを触ったことがない方は試してみてください。効率的にCI/CDを実践していきましょう。