Ubuntu Weekly Recipe

第641回LXDとmicrok8sでシングルサーバーをKubernetesクラスターにする

第560回のmicrok8sでお手軽Kubernetes環境構築では、⁠シングルノードのみに対応したKubernetes環境構築ツール」としてmicrok8sを紹介しました。その後、このmicrok8sは大幅な進化を遂げて「特定のプロダクション用途でも使える」までになっています。今回はそのmicrok8sに最近追加された、高可用性クラスター機能について紹介しましょう。

密に開発されクラスターにも対応したmicrok8s

第560回の記事が公開されたのはmicrok8sのv1.13がリリースされ、Canonicalとしてもmicrok8sの利用をアピールしだした時期でした。当時はシングルノードにしか対応していないことに加えて、ARM64のサポートを拡充していったことからもわかるように、開発者によるKubernetesの学習用や組み込み用のシンプルなアプリケーションの実行用を主なユースケースとして想定していたようです。

しかしながら世の中の「簡単に導入できるKubernetes環境」への渇望は凄まじく、すでに同等のMinikubeというツールがあるにも関わらず、microk8sの開発者も機能もどんどん増えていくことになりました。第560回ではmicrok8sの「ウィークポイント」として紹介していたマルチノード対応も半年後の2019年9月のv1.16で正式に対応していますし、1年後のv1.18ではWindowsやmacOSへのインストールもサポートされるようになりました[1]⁠。今年の8月にリリースされたv1.19に至っては、高可用性クラスタリング機能にも対応したのです[2]⁠。

Canonicalによるアナウンスでも、組み込み機器用・学習用としてだけでなく、日常的なメンテナンスが難しい環境での利用も提案しており、Canonicalによるエンタープライズ向けのサポートを行うことも明言しています[3]⁠。

他にもKubeflowやMultusといった人気のアドオンもサポートしている上に、⁠ぐぐったらそれなりに使っている人がいる」ようになったため、⁠Kubernetesを試せる環境を、ちゃちゃーっと社内に用意しておいてくれない?」とかいう上司の非情な要求にも対応できるソリューションに成長したのです。

今回はこのmicrok8sの、クラスター機能と高可用性機能(High Availability機能)について紹介します。とは言え、一般のご家庭にはクラスターを組める台数のマシンは存在しないでしょう。そこでLXDと組み合わせて、⁠ちょっと強い1台のマシン」の上でクラスター化を試みます。

仮想マシン版LXDインスタンスの準備

microk8sそのものはただのKubernetesのインストーラーでしかありませんので、コンテナ版のLXDインスタンスの上でも動きます。しかしながらコンテナ版のLXDでKubernetesを動かすには、名前空間のネストやその他各種権限の調整が必要です。また物理マシンのクラスターをエミュレーションする観点に立つと、仮想マシンを利用したほうがより実態に近くなります。よって今回は仮想マシン版のインスタンスで構築します[4]⁠。

おおよそmicrok8sが動くマシンに必要なリソースは次のとおりです。

  • CPU:「2コア x ノードの数」だけCPUコアが必要です。ただしKVMの場合はインスタンスがCPUを占用する必要はありませんし、昨今のそれなりのマシンはCPUのコアが十分に多いと思いますので、そこまで気にする必要はないでしょう。
  • メモリー:最低でも「2GiB x ノードの数」はほしいところです。これもノートPCでもない限り、困ることはないでしょう。
  • ストレージ:microk8sの上で何を動かすかに強く依存します。今回の手順のように本当に小さなコンテナを動かすだけであれば10GiBでも十分です。

ちなみにVirtualBox等の上にインストールしたUbuntuの上で構築することも可能です。VirtualBoxの上で仮想マシン版のLXDを動かす場合は、Nested KVM機能を有効化しておいてください。LXDで仮想マシンインスタンスを作っている部分をVirtualBox上のUbuntuインスタンスに置き換えてもらっても問題ありません[5]⁠。もちろんVirtualBoxのインスタンスそのものに、上記に準じたスペックが必要になります。

では、実際にUbuntu 20.04 LTS上のLXD 4.0を利用して仮想マシンを作成しましょう。LXDの仮想マシン機能に関する詳細は第609回のLXDからコンテナではなく仮想マシンを起動するを参照してください。ちなみに当時のLXD 3.19では、cloud-initを利用してアカウントを作成する必要がありましたが、LXD 4.0までに仮想マシン機能が大幅にブラッシュアップされた結果、その手順は不要になりました。LXDそのものについては第521回から始まるLXD関連の記事で解説しています。

HA機能を利用するためには最低でも3台のマシンが必要です。よってまずは以下の手順で3台の仮想マシンを作成してください。

$ lxc launch ubuntu:20.04 k8s0 --vm -c limits.cpu=2 -c limits.memory=4GiB
$ lxc launch ubuntu:20.04 k8s1 --vm -c limits.cpu=2 -c limits.memory=4GiB
$ lxc launch ubuntu:20.04 k8s2 --vm -c limits.cpu=2 -c limits.memory=4GiB

--vmで仮想マシンの作成を指定しています。

LXDの仮想マシンインスタンスは何も指定しないと「CPU 1個、メモリ1GiB、ストレージ10GiB」で作成してしまいます。そこで上記ではCPUの数とメモリのサイズを明示的にしています。インスタンス作成後にlxc config set インスタンス名 limits.memory 4GiBと指定してもかまいません。他にもインスタンスタイプで指定する方法もあります。これはAWSのように-t t2.microのような名前で一括してリソースを設定する方法です。名前とリソースの対応表はinstance-typeリポジトリからリンクしているURLを参照してください。

ストレージのサイズも変えたい場合は次のように実行してください。

$ lxc stop k8s0
$ lxc config device override k8s0 root size 40GiB
$ lxc start k8s0

仮想マシンインスタンスの場合、ストレージサイズはシャットダウン状態でのみ変更できます。そこで一旦lxc stopなどでシャットダウンした上で実行してください。lxc config device overrideはルートファイルシステムやeth0のように、プロファイルから継承・生成されたデバイスに対して特定の設定だけを変更したい場合に有効なコマンドです。

必要ならミラーサーバーを設定し、パッケージを最新に更新して、再起動しておきましょう。

$ lxc exec k8s0 -- sed -i 's/archive.ubuntu/jp.archive.ubuntu/' /etc/apt/sources.list
$ lxc exec k8s0 -- sh -c 'apt update && apt full-upgrade -y'
$ lxc restart k8s0 k8s1 k8s2

この3台の仮想マシンインスタンスがKubernetesクラスターの「ノード」になります。ちなみに仮想マシンの場合、lxc restartに時間がかかってタイムアウトエラーが発生するかもしれません。その場合は、lxc statusですべてシャットダウンされたかを確認した上で、手動でlxc startを実行してください。

microk8sをインストールしてクラスター化する

次に各ノードにmicrok8sをインストールします。手順自体は第560回と同じです。

$ lxc exec k8s0 -- snap install microk8s --classic --channel=1.19
microk8s (1.19/stable) v1.19.2 from Canonical✓ installed

--channel=1.19と指定することで、v1.19の最新安定版をインストールしています。microk8sの場合は、バージョンがそのままKubernetsのバージョンに紐付いているため、上記のように明示的にチャンネルを指定したほうが良いでしょう。そうしないと、たとえばv1.20がリリースされたときに自動的にv1.20に(つまりKubernetes 1.20に)アップグレードされてしまいます。

ここではk8s0ノードにのみインストールしていますが、他のノードにも同じようにインストールしておいてください。

インストールが完了したら次のコマンドを実行し、Kubernetesのセットアップができるまでしばらく待ちましょう。

$ lxc exec k8s0 -- microk8s status --wait-ready
microk8s is running
high-availability: no
  datastore master nodes: 127.0.0.1:19001
  datastore standby nodes: none
addons:
  enabled:
    ha-cluster           # Configure high availability on the current node
  disabled:
    ambassador           # Ambassador API Gateway and Ingress
    cilium               # SDN, fast with full network policy
    dashboard            # The Kubernetes dashboard
    dns                  # CoreDNS
    fluentd              # Elasticsearch-Fluentd-Kibana logging and monitoring
    gpu                  # Automatic enablement of Nvidia CUDA
    helm                 # Helm 2 - the package manager for Kubernetes
    helm3                # Helm 3 - Kubernetes package manager
    host-access          # Allow Pods connecting to Host services smoothly
    ingress              # Ingress controller for external access
    istio                # Core Istio service mesh services
    jaeger               # Kubernetes Jaeger operator with its simple config
    knative              # The Knative framework on Kubernetes.
    kubeflow             # Kubeflow for easy ML deployments
    linkerd              # Linkerd is a service mesh for Kubernetes and other frameworks
    metallb              # Loadbalancer for your Kubernetes cluster
    metrics-server       # K8s Metrics Server for API access to service metrics
    multus               # Multus CNI enables attaching multiple network interfaces to pods
    prometheus           # Prometheus operator for monitoring and logging
    rbac                 # Role-Based Access Control for authorisation
    registry             # Private image registry exposed on localhost:32000
    storage              # Storage class; allocates storage from host directory

上記のようにステータスが表示されたら準備完了です。これも他のノードにも実行しておきます。このタイミングではすべてのノードが独立しているため、⁠high-availability: no」となり、マスターノードには自分自身のみが存在する状態となっています。ちなみにv1.19から、ha-clusterアドオンが自動的に有効化されるようになりました。

Kubernetesとしては動作している状態になったので、たとえば次のような方法でkubectlを実行できます。

$ lxc exec k8s0 microk8s kubectl get nodes
NAME   STATUS   ROLES    AGE     VERSION
k8s0   Ready    <none>   2m33s   v1.19.2-34+1b3fa60b402c1c

microk8s kubectlとなっていることに注意してください。もし毎回microk8sと入力したくないのであれば、alias kubectl='microk8s kubectl'~/.bash_aliasesに書いておくと良いでしょう。ただしlxc exec経由で実行する場合は、エイリアス設定が効かないので注意してください。第560回で紹介したようにsnap alias microk8s.kubectl kubectlを実行する方法も引き続き利用可能です[6]⁠。

クラスター化は、特定のノードでノード連結させるためのクレデンシャルを生成・表示し、それ以外のノードからそのクレデンシャルを用いて登録要求を出すという流れになります。今回は3台のノードがすべてマスターノードになるため、どのノードでクレデンシャルを作ってもかまいません。そこでk8s0ノードで生成し、k8s1とk8s2から登録することにしましょう。

クレデンシャルの生成はadd-nodeサブコマンドを利用します。

$ lxc exec k8s0 microk8s add-node
From the node you wish to join to this cluster, run the following:
microk8s join 10.203.13.213:25000/b88027fd3b7980a9d61a28ff4f00388f

If the node you are adding is not reachable through the default interface you can use one of the following:
 microk8s join 10.203.13.213:25000/b88027fd3b7980a9d61a28ff4f00388f
 microk8s join 10.1.150.64:25000/b88027fd3b7980a9d61a28ff4f00388f

「IPアドレス:ポート番号/クレデンシャル」の形式で表示されるので、それを登録する側のノード上でjoinサブコマンドに渡す形になります。一度joinするとそのクレデンシャルは使えなくなるので、必要な数だけadd-nodeを実行してください。複数のネットワークインターフェースを持つマシンだと、別のノードから到達できるインターフェースが限られているかもしれません。最後の2行のように、インターフェースごとにコマンド例を表示しているので、環境に合わせて選んでください。

登録する側のノード(今回だとk8s1とk8s2)joinサブコマンドを実行します。

$ lxc exec k8s1 -- microk8s join 10.203.13.213:25000/b88027fd3b7980a9d61a28ff4f00388f
Contacting cluster at 10.203.13.213
Waiting for this node to finish joining the cluster. ..

登録までにはしばらく時間がかかります。おとなしく待ちましょう。無事に登録できると、次のようにkubectl get nodesで登録されたノードがすべて表示されるようになります。

$ lxc exec k8s0 microk8s kubectl get nodes
NAME   STATUS   ROLES    AGE     VERSION
k8s0   Ready    <none>   6m35s   v1.19.2-34+1b3fa60b402c1c
k8s1   Ready    <none>   87s     v1.19.2-34+1b3fa60b402c1c
k8s2   Ready    <none>   86s     v1.19.2-34+1b3fa60b402c1c

また、microk8s statusの先頭が次のような表記になります。

$ lxc exec k8s0 -- microk8s status --wait-ready
microk8s is running
high-availability: yes
  datastore master nodes: 10.203.13.213:19001 10.203.13.208:19001 10.203.13.95:19001
  datastore standby nodes: none
(後略)

ポイントはhigh-availability: yesになっていること、そしてmaster nodesにクラスター化した3台のノードのIPアドレスが表示されていることです。

ちなみに「クラスター化」だけであれば、2台でもかまいません。しかしながらHA機能に対応するためには「最低3台」のノードが必要になります。

マスターノードにログインする

3台のノードがクラスター化されました。このため、マスターノードであればどのノードからでもkubectlで操作可能です[7]⁠。そこでlxc execの入力を省くために、ここからはk8s0ノードにログインして、そこから操作することにします。まずはk8s0ノードに最初から作られているubuntuアカウントで、SSHログインできるようにしておきましょう。

$ lxc exec k8s0 -- sudo -i -u ubuntu ssh-import-id gh:(GitHubのアカウント名)

上記のように実行することで、GitHubに登録されているSSHの公開鍵をk8s0ノードのubuntuユーザーのauthorized_keysにインポートしてくれます。あとはk8s0のIPアドレス(たとえばlxc listで確認可能)にログインします。

$ ssh ubuntu@10.203.13.213

試しにmicrok8s kubectlを実行すると次のようなメッセージが表示されます。

k8s0$ microk8s kubectl get nodes
Insufficient permissions to access MicroK8s.
You can either try again with sudo or add the user ubuntu to the 'microk8s' group:

    sudo usermod -a -G microk8s ubuntu
    sudo chown -f -R ubuntu ~/.kube

The new group will be available on the user's next login.

microk8sはいくつか管理者権限が必要な操作があります。microk8sグループに入っておくと、これらの処理を実行できるようになるわけです。chownしているのは、microk8sインストール直後に自動的に作られた~/.kubeの一部が管理者権限であるためです。前述の手順だと、最初はlxc execで実行している都合上、/root/.kubeになっているので、chownは実施しなくてもかまいません。

usermodchownを実行し、ログインし直したら、きちんと動くか確認しておきましょう。

k8s0$ microk8s kubectl get nodes
NAME   STATUS   ROLES    AGE     VERSION
k8s0   Ready    <none>   6m35s   v1.19.2-34+1b3fa60b402c1c
k8s1   Ready    <none>   87s     v1.19.2-34+1b3fa60b402c1c
k8s2   Ready    <none>   86s     v1.19.2-34+1b3fa60b402c1c

k8s0:$ microk8s status
microk8s is running
high-availability: yes
  datastore master nodes: 10.203.13.213:19001 10.203.13.208:19001 10.203.13.95:19001
  datastore standby nodes: none
(後略)

microbotのデプロイ

HA機能の動作確認を行う前に、テスト用にmicrobotをデプロイしておきます。これは単なるホスト名をウェブページとして表示するだけのHTTPサーバーです。

k8s0$ microk8s kubectl create deployment microbot --image=dontrebootme/microbot:v1
deployment.apps/microbot created

kubectlコマンドで実際にデプロイされていることを確認しましょう。

k8s0$ microk8s kubectl get all
NAME                            READY   STATUS    RESTARTS   AGE
pod/microbot-5f5499d479-bjjxw   1/1     Running   0          31s

NAME                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.152.183.1   <none>        443/TCP   32m

NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/microbot   1/1     1            1           33s

NAME                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/microbot-5f5499d479   1         1         1       32s

ノードの外からアクセスできるように、NodePortタイプでexposeしておきます。

k8s0$ microk8s kubectl expose deployment microbot --type=NodePort --port=80 --name=microbot-service
service/microbot-service exposed

k8s0$ microk8s kubectl get service microbot-service
NAME               TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
microbot-service   NodePort   10.152.183.25   <none>        80:30085/TCP   27s

今回はExternal-IPは割り当てていません。各ノードのポート300085で受け付けていることがわかるため、試しにLXDが動いているホスト上からcurlで取得してみましょう。k8s0ノードの中からではないことに注意してください。

$ curl 10.203.13.213:30085
<!DOCTYPE html>
<html>
  <style type="text/css">
    .centered
      {
      text-align:center;
      margin-top:0px;
      margin-bottom:0px;
      padding:0px;
      }
  </style>
  <body>
    <p class="centered"><img src="microbot.png" alt="microbot"/></p>
    <p class="centered">Container hostname: microbot-5f5499d479-bjjxw</p>
  </body>
</html>

Container hostnameとして表示されているホスト名が、前述のkubectl get allで表示されていたPodのインスタンス名と一致していることがわかります。

本来ならここでウェブブラウザーを使って表示することも試してみたいところではありますが、少し手間がかかるので省略します。コンテナ版のLXDの場合、proxyデバイスを作成することで、簡単にホストの外からコンテナへアクセスできるように設定できます。しかしながらこの設定は、現時点ではコンテナのみ利用可能で、仮想マシンでは使えません。よってもし設定するとなると、自分でiptables/nftablesの設定を記述するか、Nginxのようなリバースプロキシーをセットアップする必要があります。

ちなみに上記の環境だとPodはk8s2ノード上で動いているようです。

k8s0$ microk8s kubectl describe pod microbot | grep Node:
Node:         k8s2/10.203.13.208

スタンバイノードの作成とフェイルオーバーのテスト

最後に実際にmicrobotが動いているノードを落として、Podが他のノードにフェイルオーバーするかを確認してみましょう。

まず、スタンバイノードを作成します。microk8sのHAクラスターでは最低3台のマスターノード(voter)が必要です。マスターノード上の特定のノードが状態変更を受けると、マスターノード間で調停を行い、実際に適用するかどうかを判断し、データベースをレプリケーションします。それに対して「スタンバイノード」は、レプリケーションは行われるものの調停には参加しません。何らかの理由でマスターノードの特定のノードが応答しなくなったときに、スタンバイノードのいずれかが、マスターノードに昇格します。

HAクラスターに参加するノードが3台未満になると、HA機能は無効化されます。つまり障害発生時もHA機能が維持されるためには、3台のマスターノードに加えて、最低1台のスタンバイノードが必要です。スタンバイノードそのものは、他のノードと同じように作成します。ここでは新規にk8s3ノードをスタンバイノードとして作りましょう。

$ lxc launch ubuntu:20.04 k8s3 --vm -c limits.memory=4GB
$ lxc exec k8s3 -- sed -i 's/archive.ubuntu/jp.archive.ubuntu/' /etc/apt/sources.list
$ lxc exec k8s3 -- sh -c 'apt update && apt full-upgrade -y'
$ lxc restart k8s3
$ lxc exec k8s3 -- snap install microk8s --classic --channel=1.19
$ lxc exec k8s3 -- microk8s status --wait-ready

さらにk8s0ノード上で、microk8s add-nodeした結果をもとに、k8s3ノードをmicrok8s joinしておいてください。これでk8s3ノードがHAクラスターに追加されたことになります。追加後、少し待ってからmicrok8s statusを実行するとスタンバイノードが追加されていることがわかりますね。

k8s0$ microk8s status
microk8s is running
high-availability: yes
  datastore master nodes: 10.203.13.213:19001 10.203.13.95:19001 10.203.13.208:19001
  datastore standby nodes: 10.203.13.186:19001

k8s0$ microk8s kubectl get nodes
NAME   STATUS   ROLES    AGE   VERSION
k8s2   Ready    <none>   62m   v1.19.2-34+1b3fa60b402c1c
k8s1   Ready    <none>   62m   v1.19.2-34+1b3fa60b402c1c
k8s3   Ready    <none>   39s   v1.19.2-34+1b3fa60b402c1c
k8s0   Ready    <none>   67m   v1.19.2-34+1b3fa60b402c1c

ここでmicrobotが動いているk8s2ノードを落としてしまいましょう。まずmicrobotのノードの位置は次のように確認できます。

k8s0$ microk8s kubectl describe pod microbot | grep Node:
Node:         k8s2/10.203.13.208

障害発生を装うならk8s2の仮想マシンインスタンスを落としてしまうという手もあります。今回はもう少しおとなしく「正式な手順でHAクラスターから離脱する」という方法をとります。特定のノードのメンテナンスのために、クラスターから取り外したい場合に使われる方法です。これは離脱したノード上で、microks leaveコマンドを実行することで実現できます。

$ lxc exec k8s2 -- microk8s leave
Generating new cluster certificates.
Waiting for node to start.

しばらくしてからk8s0側から見ると、k8s2ノードがNotReadyになったことがわかります。

k8s0$ microk8s kubectl get nodes
NAME   STATUS     ROLES    AGE     VERSION
k8s1   Ready      <none>   64m     v1.19.2-34+1b3fa60b402c1c
k8s3   Ready      <none>   2m28s   v1.19.2-34+1b3fa60b402c1c
k8s0   Ready      <none>   69m     v1.19.2-34+1b3fa60b402c1c
k8s2   NotReady   <none>   64m     v1.19.2-34+1b3fa60b402c1c

取り外されたノードを完全にクラスターから除外するにはmicrok8s remove-nodeを実行します。

k8s0$ microk8s remove-node k8s2
k8s0$ microk8s kubectl get nodes
NAME   STATUS   ROLES    AGE    VERSION
k8s1   Ready    <none>   64m    v1.19.2-34+1b3fa60b402c1c
k8s3   Ready    <none>   3m2s   v1.19.2-34+1b3fa60b402c1c
k8s0   Ready    <none>   69m    v1.19.2-34+1b3fa60b402c1c

ステータスを見ると、スタンバイノードだったk8s3(10.203.13.186)がマスターノードに昇格していることがわかります。

k8s0$ microk8s status
microk8s is running
high-availability: yes
  datastore master nodes: 10.203.13.213:19001 10.203.13.95:19001 10.203.13.186:19001
  datastore standby nodes: none

ここから数秒から数十秒の間に、フェイルオーバーが実施されます。つまりk8s2で動いていたmicrobotインスタンスが、生きているノードのいずれかに移動するわけです。kubectl describe podで確認してみましょう。

k8s0$ microk8s kubectl describe pod microbot | grep Node:
Node:         k8s3/10.203.13.186

どうやらk8s3ノードに移行したようです。

もちろん、ノードの外からアクセスする方法はフェイルオーバー前後で変わりません。前回と同様にcurlを用いて同じURLにアクセスできます。

$ curl 10.203.13.213:30085
<!DOCTYPE html>
<html>
  <style type="text/css">
    .centered
      {
      text-align:center;
      margin-top:0px;
      margin-bottom:0px;
      padding:0px;
      }
  </style>
  <body>
    <p class="centered"><img src="microbot.png" alt="microbot"/></p>
    <p class="centered">Container hostname: microbot-5f5499d479-bkxvd</p>
  </body>
</html>

ポイントはContainer hostnameの値が前回microbot-5f5499d479-bjjxwから末尾だけ変わっていることです。これは同じイメージを利用しているものの、コンテナのインスタンスが変わったことを示しています。

このようにLXDとmicrok8sを組み合わせると、簡単にKubernetesのHAクラスターを構築できます。本番環境ほどのマシン台数は用意できないけれども、ちょっと動作を確認したい場合に使えるのではないでしょうか。

おすすめ記事

記事・ニュース一覧