まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:k8s

コンテナオーケストレーション環境である kubernetes(以下 k8s)上で PC-DOS エミュレーターである DOSBOX を動かしてみました。

なお、以下で紹介する k8s の環境としては以前にこのブログで紹介した minikube 環境を使います。minikube の(Windows 10 + WSL 向けの)環境構築手順はこちらです:
Windows 10 に minikube を導入して WSL から利用する


また k8s にデプロイする DOSBOX のイメージは Docker Hub に公開されていたこれを使うことにします:
DOSBox for Docker Server


上記ページによると、このイメージは以下2つの特徴がある模様です:
(1)起動後、5901 番ポートで VNC 接続を待ち受ける(つまり VNC 経由で DOSBOX に接続する)
(2)VNC のパスワードは環境変数 VNCPASSWORD で指定する


ではこのことを理解した上で、まずはイメージを k8s 環境へデプロイします:

環境変数 VNCPASSWORD=P@ssw0rd を指定して jgoerzen/dosbox を k8s に dosbox という名前でデプロイ
$ kubectl run dosbox --image=jgoerzen/dosbox --env=VNCPASSWORD=P@ssw0rd

kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/dosbox created

dosbox のポート 5901 を expose
$ kubectl expose deployment dosbox --type="NodePort" --port=5901

service/dosbox exposed

これでイメージのデプロイは完了し、利用可能になっているはずです。最後に利用するために必要な情報を確認しておきます:

IP アドレスを確認
$ kubectl cluster-info

Kubernetes master is running at https://192.168.99.100:8443
KubeDNS is running at https://192.168.99.100:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.


外部公開ポート番号を確認
$ kubectl get svc

NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
dosbox       NodePort    10.105.213.111                 5901:32286/TCP   9s
kubernetes   ClusterIP   10.96.0.1                      443/TCP          103d

上記の結果から 192.168.99.100:32286 で VNC 接続します:
2019122002


接続時にパスワードを聞かれます。デプロイ時に環境変数 VNCPASSWORD で指定した文字列(上記の場合は P@ssw0rd)を入力します:
2019122003


正しく接続できると xterm のターミナルと、DOSBOX アプリが起動した X Window が表示され、DOSBOX が利用できるようになります:
2019122001


これで DOS 環境を共有するハードルを1つ超えた、かも!


以前にラズパイ3(メモリ1GB)を3台使ってコンテナクラスタのオーケストレーション環境を作ったことがありました:
ラズベリーパイと鳩サブレ缶で docker swarm クラスタを構築する
ラズベリーパイと鳩サブレ缶で kubernetes クラスタを構築する

8ea25b3e


上記の上では docker swarm 環境、下では kubernetes 環境を構築しました。手順などの詳細はそれぞれのリンク先を参照していただきたいのですが、この時は kubernetes 1.8 を使ってクラスタを構築しました。kubernetes の環境としては作れたのですが、(案の定というか)メモリが圧倒的に足りなさすぎて実用にはほど遠い kubernetes 環境となってしまいました。マイナーな環境であることは理解しつつ、今でもラズパイ3でのオーケストレーション環境としては docker swarm が現実的なのかなあ、と感じています。


さて時は流れ、ラズパイ4が発売され、日本でも入手できるようになりました。CPU 等の強化もありますが、なんといっても4GBのメモリモデルを選ぶことができるようになったモデルです。上述のメモリ不足が解消される期待ができる上に CPU 等も強化されているわけなので、まともに動く(苦笑) kubernetes 環境構築にも期待できそうです。早速使ってみました。


【環境構築手順】
基本的には特別なことはしていないのですが、一応一通りの説明をしていきます。なお今回のクラスタ環境には計3台のラズパイ4を使って1台のマスターノードと2台のワーカーノードを構築しています。以下ではそれぞれ k8s-master-01, k8s-worker-01, k8s-worker-02 という名称で呼ぶことにします。

【3台共通で行う作業】
全てのノードに共通で行う作業は以下になります:
・Raspbian OS Buster 最新版の導入
・ネットワークなどを設定して再起動
・docker インストール
・kubectl などのコマンドをインストール

まず OS はラズパイ4向けにリリースされた Raspbian OS Buster を使います。こちらのサイトから "Raspbian Buster Lite" と書かれたイメージの最新版をダウンロード&展開して、MicroSD カードに焼き付けてください(私が試した時は "September 2019" バージョンが最新でした):
2019121601


起動後、ネットワークを有効にした上でターミナルから以下のコマンドを実行し、各種モジュールを最新版にしておきます。ここで少し時間がかかります:
$ sudo apt-get update -y

$ sudo apt-get upgrade -y

この後で一度再起動をかけるのですが、その再起動前に変更しておくべき項目がいくつかあります。まずは /boot/cmdline.txt を編集します。これは再起動の後 kubernetes 関連のコマンドを導入して動かす際に必要な cgroups の設定です:
$ sudo vi /boot/cmdline.txt

(cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1 を追加)

同様にして再起動前に以下のコマンドを実行してスワップメモリを無効&再起動後も無効に設定します。最近の kubernetes はスワップメモリがあると起動に失敗するので、予め無効にしておくための設定です:
$ sudo swapoff --all

$ sudo apt-get purge -y --auto-remove dphys-swapfile

$ sudo rm -fr /var/swap

また IP アドレスやホスト名の設定も行っておきましょう。以下の説明では3台のラズパイがそれぞれ以下のような固定 IP アドレス及びホスト名で動かす想定とします(IP アドレスなど設定が異なる場合は適宜読み替えてください):
ホスト名IPアドレスノードの用途
k8s-master-01192.168.1.200マスター
k8s-worker-01192.168.1.201ワーカー
k8s-worker-02192.168.1.202ワーカー


まず /etc/dhcpcd.conf を編集して以下の部分を追加し、固定 IP アドレスを取得するように変更します:
$ sudo vi /etc/dhcpcd.conf

(以下は無線 LAN(wlan0) で 192.168.1.200 に設定する場合)
interface wlan0
static ip_address=192.168.1.200/24
static routers=192.168.1.1
static domain_name_servers=192.168.1.1 8.8.8.8

次に /etc/hostname を編集して、raspberrypi と書かれた内容をそれぞれのホスト名で書き換えます:
$ sudo vi /etc/hostname

(k8s-master-01 の場合)
raspberrypi  k8s-master-01

また /etc/hosts を編集して、他の2ノードにも名前でアクセスできるように名前解決ルールを記載しておきます:
$ sudo vi /etc/hostname

(k8s-master-01 の場合)
127.0.1.1   raspberrypi  k8s-master-01

(以下は3台全てに追加する3行)
192.168.1.200  k8s-master-01
192.168.1.201  k8s-worker-01
192.168.1.202  k8s-worker-02

また以下は必須ではないのですが、固定の IP アドレスを設定する場合は SSH でアクセスできるようになるとこの後の作業が(直接ログインの必要がなくなり、リモートから SSH 接続後に作業できるようになって)楽なので、SSH 接続を有効にしておくと便利です。sudo raspi-config を実行してから、"Interfacing options" - "SSH" を選択して、SSH を有効にしておきます:
2019121602


ここまでの作業が済んだら一度再起動をかけます:
$ sudo shutdown -r now

再起動後、再度(SSH などで)ログインして、SSH 鍵を3台のラズパイ間で共有します。この作業は3台のラズパイが全て(上述の IP アドレスの再設定などを行った上での)再起動をして、全てネットワークに接続して稼働している状態で3台全てで行う必要があります:
$ ssh-keygen -t rsa

(いろいろ聞かれるけど、全て無指定のまま Enter でもOK)
$ ssh-copy-id k8s-master-01 $ ssh-copy-id k8s-worker-01 $ ssh-copy-id k8s-worker-02

次に docker を導入します:
$ curl -sSL https://get.docker.com/ | sh

$ sudo usermod -aG docker pi

(この後、一度ターミナルをログアウトして抜けて、再度ターミナルを開く)

次に kubelet, kubeadm, kubectl, kubenetes-cni などをインストールしますが、その準備として新しいリポジトリを登録する必要があります。まずはその準備として apt コマンドを https のリポジトリでも実行できるよう環境を用意しておきます:
$ sudo apt-get install -y apt-transport-https

そしてリポジトリのソースリストに kubernetes を追加します:
$ curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -

$ echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list

$ sudo apt-get update

ここまでの準備で kubectl などのコマンドを導入することができるようになったので、まとめて導入し、更にバージョンが変更されることがないよう固定します:
$ sudo apt-get update

$ sudo apt-get install kubelet kubeadm kubectl kubernetes-cni

$ sudo apt-mark hold kubelet kubeadm kubectl kubernetes-cni

3台共通で行う導入作業は以上です。


【マスターノード上で行う作業】
マスターノード上では flannel を使って kubernetes クラスタのコントロールプレーンを初期化します(このコマンド終了後に(kubeadm join)で始まる行が表示されるので、その内容を保存しておきます):
$ sudo kubeadm init --pod-network-cidr=10.244.0.0/16

上述コマンドの実行結果に含まれるコマンドを実行します:
$ mkdir -p $HOME/.kube

$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config

$ sudo chown $(id -u):$(id -g) $HOME/.kube/config

最後にコンテナ間通信のためのモジュール flannel を導入します:
$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/62e44c867a2846fefb68bd5f178daf4da3095ccb/Documentation/kube-flannel.yml

これでマスターノード上の作業は完了です。以下の kubectl version コマンドを実行して正しく結果が返ってくることを確認しておきます:
$ kubectl version

Client Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.0", GitCommit:"70132b0f130acc0bed193d9ba59dd186f0e634cf", GitTreeState:"clean", BuildDate:"2019-12-07T21:20:10Z", GoVersion:"go1.13.4", Compiler:"gc", Platform:"linux/arm"}
Server Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.0", GitCommit:"70132b0f130acc0bed193d9ba59dd186f0e634cf", GitTreeState:"clean", BuildDate:"2019-12-07T21:12:17Z", GoVersion:"go1.13.4", Compiler:"gc", Platform:"linux/arm"}


【ワーカーノード上で行う作業】
続いて上述の kubeadm init コマンド正常終了時に出ていた、kubeadm join で始まるコマンドをワーカーノード上で実行すると、用意したマスターノードにワーカーノードが追加される形になります:
$ sudo kubeadm join 192.168.1.200:6443 --token XXXXXXXXXXXXXXXXX --discovery-token-ca-cert-hash sha256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

全てのワーカーノードが Ready ステータスになるまで少し時間がかかりますが、これでワーカーノードがマスターノードに紐付けられる形で追加されます。この状態になっていることを確認するためにマスターノード上で kubectl get nodes コマンドを実行して、2つのワーカーノードが追加されていることを確認します:
$ kubectl get nodes

NAME            STATUS   ROLES    AGE     VERSION
k8s-master-01   Ready    master   4d7h    v1.17.0
k8s-worker-01   Ready             2d23h   v1.17.0
k8s-worker-02   Ready             2d23h   v1.17.0

これで3台のラズパイ(4)を使った kubernetes クラスタ環境が構築できました。


【動作確認手順】
ではこの環境を使って実際にアプリケーションを動かしてみます。動作確認用にラズパイ(arm32v7)アーキテクチャ用のウェブアプリケーション docker イメージを以下に用意しました。よかったら使ってください:
https://hub.docker.com/repository/docker/dotnsf/hostname

↑このアプリケーションは実行している環境内のファイルシステムから /etc/hostname を読み込んで、そのまま text/plain で表示する(つまり VM やコンテナの内部的なホスト名を返す)だけのシンプルなアプリケーションです。なお特に指定しない場合は 3000 番ポートで待ち受けます。ソースコードに興味がある人は以下を参照ください:
https://github.com/dotnsf/hostname


ではこのイメージを構築した kubernetes 環境内で動かしてみます。以下は全てマスターノード内のターミナルで行う作業です。最初にアプリケーションを hostname という名前で deploy して、3000 番ポートを expose します。続けて pod の状態を確認して(下の例では hostname-86cfdc6cbf-psgdp という名前で動いています)、外部アクセス用のポート番号を確認します(下の例では 30652 番ポートで公開されています):
$ kubectl run hostname --image=dotnsf/hostname

$ kubectl expose deployment hostname --type="NodePort" --port=3000

$ kubectl get pods

NAME                        READY   STATUS    RESTARTS   AGE
hostname-86cfdc6cbf-psgdp   1/1     Running   0          2m12s

$ kubectl get services

NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
hostname     NodePort    10.96.124.229                 3000:30652/TCP   2m22s
kubernetes   ClusterIP   10.96.0.1                     443/TCP          4d7h

deploy された hostname-86cfdc6cbf の各 pod がどのワーカーノードで動いているかを確認します(下の例では k8s-worker-02 で動いていることが確認できます):
$ kubectl describe pod hostname-86cfdc6cbf

Name:         hostname-86cfdc6cbf-psgdp
Namespace:    default
Priority:     0
Node:         k8s-worker-02/192.168.1.202
Start Time:   Tue, 17 Dec 2019 07:55:38 +0900
Labels:       pod-template-hash=86cfdc6cbf
              run=hostname
Annotations:  <none>
Status:       Running
IP:           10.244.2.7
IPs:
  IP:           10.244.2.7
Controlled By:  ReplicaSet/hostname-86cfdc6cbf
Containers:
  hostname:
    Container ID:   docker://40fd3c5402c7617aa390ef73acdc6d29502788fd828d8307d1d06f9a00c3081c
    Image:          dotnsf/hostname
    Image ID:       docker-pullable://dotnsf/hostname@sha256:5052df05816b24f6b27da8e3ef75a912944747234118fe25d7bd054f841ee6f0
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Tue, 17 Dec 2019 07:56:23 +0900
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-6qsnh (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  default-token-6qsnh:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-6qsnh
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:          <none>

アプリケーションにアクセスして、アプリケーションの動作を確認してみます。上述の手順で確認した IP アドレス(ホスト名)とポート番号を使って curl コマンドでアクセスしてみます:
$ curl http://k8s-worker-02:30652/

hostname-86cfdc6cbf-psgdp


期待通りの結果(/etc/hostname の中身)が返ってきました。どうやら構築したラズパイ4の kubernetes クラスタ環境が正しく動作していることを確認できました!


最後に、 deploy したアプリケーションがこの状態では1インスタンスで動作していますが、これを3インスタンスで動作するようにスケールさせてみます:
$ kubectl scale --replicas=3 deployment hostname

各 Pods の状態を確認して、3インスタンスでの動作に切り替わったことを確認します:
$ kubectl get pods

NAME                        READY   STATUS    RESTARTS   AGE
hostname-86cfdc6cbf-gh88s   1/1     Running   0          7m3s
hostname-86cfdc6cbf-h4x4z   1/1     Running   0          7m3s
hostname-86cfdc6cbf-psgdp   1/1     Running   0          16h

もともと hostname-86cfdc6cbf-psgdp 上だけで動いていたのですが、この Pod に加えて hostname-86cfdc6cbf-gh88s と hostname-86cfdc6cbf-h4x4z の2つの pods が追加されたことが確認できました。


改めて各インスタンスがどのワーカーノードで動いているのかを確認します:
$ kubectl describe pod hostname-86cfdc6cbf

Name:         hostname-86cfdc6cbf-gh88s
Namespace:    default
Priority:     0
Node:         k8s-worker-01/192.168.1.201
Start Time:   Wed, 18 Dec 2019 00:26:36 +0900
Labels:       pod-template-hash=86cfdc6cbf
              run=hostname
Annotations:  <none>
Status:       Running
IP:           10.244.1.9
IPs:
  IP:           10.244.1.9
Controlled By:  ReplicaSet/hostname-86cfdc6cbf
Containers:
  hostname:
    Container ID:   docker://923d7727f8c0fbcc7af3ee5119e60cc22bd2a0817e56e5230879df650edbdc0f
    Image:          dotnsf/hostname
    Image ID:       docker-pullable://dotnsf/hostname@sha256:5052df05816b24f6b27da8e3ef75a912944747234118fe25d7bd054f841ee6f0
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Wed, 18 Dec 2019 00:27:33 +0900
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-6qsnh (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  default-token-6qsnh:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-6qsnh
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type    Reason     Age        From                    Message
  ----    ------     ----       ----                    -------
  Normal  Scheduled  <unknown>  default-scheduler       Successfully assigned default/hostname-86cfdc6cbf-gh88s to k8s-worker-01
  Normal  Pulling    2m29s      kubelet, k8s-worker-01  Pulling image "dotnsf/hostname"
  Normal  Pulled     2m27s      kubelet, k8s-worker-01  Successfully pulled image "dotnsf/hostname"
  Normal  Created    2m3s       kubelet, k8s-worker-01  Created container hostname
  Normal  Started    117s       kubelet, k8s-worker-01  Started container hostname

Name:         hostname-86cfdc6cbf-h4x4z
Namespace:    default
Priority:     0
Node:         k8s-worker-02/192.168.1.202
Start Time:   Wed, 18 Dec 2019 00:26:36 +0900
Labels:       pod-template-hash=86cfdc6cbf
              run=hostname
Annotations:  <none>
Status:       Running
IP:           10.244.2.8
IPs:
  IP:           10.244.2.8
Controlled By:  ReplicaSet/hostname-86cfdc6cbf
Containers:
  hostname:
    Container ID:   docker://f2af91199fa35f6e64d717c3f101ca2e559d3136196519e73cad948a2708527a
    Image:          dotnsf/hostname
    Image ID:       docker-pullable://dotnsf/hostname@sha256:5052df05816b24f6b27da8e3ef75a912944747234118fe25d7bd054f841ee6f0
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Wed, 18 Dec 2019 00:27:28 +0900
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-6qsnh (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  default-token-6qsnh:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-6qsnh
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type    Reason     Age        From                    Message
  ----    ------     ----       ----                    -------
  Normal  Scheduled  <unknown>  default-scheduler       Successfully assigned default/hostname-86cfdc6cbf-h4x4z to k8s-worker-02
  Normal  Pulling    2m27s      kubelet, k8s-worker-02  Pulling image "dotnsf/hostname"
  Normal  Pulled     2m24s      kubelet, k8s-worker-02  Successfully pulled image "dotnsf/hostname"
  Normal  Created    2m2s       kubelet, k8s-worker-02  Created container hostname
  Normal  Started    119s       kubelet, k8s-worker-02  Started container hostname

Name:         hostname-86cfdc6cbf-psgdp
Namespace:    default
Priority:     0
Node:         k8s-worker-02/192.168.1.202
Start Time:   Tue, 17 Dec 2019 07:55:38 +0900
Labels:       pod-template-hash=86cfdc6cbf
              run=hostname
Annotations:  <none>
Status:       Running
IP:           10.244.2.7
IPs:
  IP:           10.244.2.7
Controlled By:  ReplicaSet/hostname-86cfdc6cbf
Containers:
  hostname:
    Container ID:   docker://40fd3c5402c7617aa390ef73acdc6d29502788fd828d8307d1d06f9a00c3081c
    Image:          dotnsf/hostname
    Image ID:       docker-pullable://dotnsf/hostname@sha256:5052df05816b24f6b27da8e3ef75a912944747234118fe25d7bd054f841ee6f0
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Tue, 17 Dec 2019 07:56:23 +0900
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-6qsnh (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  default-token-6qsnh:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-6qsnh
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:          <none>


この例では hostname-86cfdc6cbf-gh88s が k8s-worker-01 上で、それ以外の hostname-86cfdc6cbf-h4x4z 、 hostname-86cfdc6cbf-gh88s は k8s-worker-02 上でそれぞれ動作することがわかりました。

では今度は k8s-worker-01 に対してアクセスしてみます:
$ curl http://k8s-worker-01:30652/
hostname-86cfdc6cbf-gh88s

k8s-worker-01 ノードへも正しくアクセスすることができるようになりました。ここまで、無線 LAN を使ったことが原因(と思われる)パフォーマンスの遅さを感じることはありましたが、ラズパイ3の頃よりはかなり速くなっています! どうやらラズパイ・クラスタ環境の構築および動作確認ができました! あとはこれをクラスタ構築用の鳩サブレ缶にセットしてあげれば完成です:
2019121800


なお、作成した deployment や service を削除するには以下のコマンドを実行します:
$ kubectl delete deployment hostname

$ kubectl delete service hostname



MIT(マサチューセッツ工科大学)が開発した教育用プログラミング環境「スクラッチ」は、ウェブブラウザでこちらのサイトを訪れることで利用することができます。一般的にはこの方法で利用することが(後述の方法と比較して常に最新版が利用できる、といったメリットもあり)推奨されます:
2019112001


最新バージョン(2019/11/18 時点では 3)と比較しての互換性などはありませんが、バージョン 1.x ではメッセージングによる外部連携を行うことができました。このバージョンは、今でもダウンロードしてインストールすることで自分の環境から利用することができます:
https://scratch.mit.edu/scratch_1.4


2019年11月現在、普通にスクラッチを利用する場合の環境は上述2つのいずれかになると思っています。ただインターネット環境に制約があるケースなど、特殊な環境でも最新版を利用することができないわけではありません。今回はそのような特殊なケースを想定し、Kubernetes (以下 k8s)環境の中にインストールしたスクラッチを使う方法を紹介します。

なお、以下つらつらと記述していますが、そんなに特別なことを書いているつもりはなく、「ごく普通に k8s に MIT スクラッチのイメージをデプロイ」しているだけです。どちらかというと自分の備忘録としてのブログエントリです。


【準備】
まず k8s 環境を用意します。独自にインストールした k8s 環境でもいいし、minikube 環境でもいいし、クラウドベンダーのサービスを使うもアリです。自分の環境からの利用に支障がないものを使ってください。なお以下では IBM Cloud から提供されている IKS(IBM Kubernetes Services) を使った例を紹介しています。IKS の場合は kubectl コマンドに加え、ibmcloud コマンドの導入も必要になります。詳しくはこちらを参照ください:
https://github.com/dotnsf/iks-handson/tree/master/Lab0

また minikube 環境を Windows 10 + WSL 環境下で作る場合の手順は以前のブログエントリで紹介したことがあります。こちらの環境を使う場合は以下を参照ください:
Windows 10 に minikube を導入して WSL から利用する


以下の手順に進む前に ibmcloud コマンドを使った IBM Cloud へのログインや環境変数の設定などを済ませておくようにしてください。詳しくは上述のリンク先を参照ください。


【デプロイ】
MIT スクラッチの docker イメージを使って k8s 上にデプロイします。今回はこのイメージを使わせていただきました:
https://hub.docker.com/r/kadok0520/mit-scratch3

(↑ MIT スクラッチ version 3 の docker イメージのようです)


デプロイ手順は以下になります(以下では mit-scratch3 という名称でデプロイしています):
$ kubectl run mit-scratch3 --image=kadok0520/mit-scratch3

$ kubectl get pod (pod の状態を参照し、 READY が 1/1 になっていることを確認)

NAME                                READY   STATUS    RESTARTS   AGE
pod/mit-scratch3-795d945bcb-2mj2b   1/1     Running   0          16h



"kubectl get all" の結果、mit-scratch3 の pod が動いていることが確認できました。

このままだと外からの利用ができないため、ポートフォワーディングを使って公開します。イメージのドキュメントによると、このイメージは内部的には 8601 番ポートを使って動作するようなので、このポート番号を指定して expose した上で、service の状態を確認します:
$ kubectl expose depoyment mit-scratch3 --type="NodePort" --port=8601

$ kubectl get service mit-scratch3 (service の状態を参照し、 POST を確認)
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE mit-scratch3 NodePort 172.21.228.178 8601:32686/TCP 16h

"kubectl get service" の結果、mit-scratch3 は 32686 番ポートで外部公開されたことが確認できました。

また、このサービスのパブリック IP アドレスも確認します。パブリック IP アドレスの確認方法は使っている k8s 環境によって異なりますが、IBM Cloud の IKS の場合であれば次のコマンドで確認できます("mycluster" 部分は作成したクラスタの名称):
$ ibmcloud ks workers mycluster

OK
ID                                                     パブリック IP   プライベート IP   フレーバー   状態     状況     ゾーン   バージョン
kube-bn92ui5d00ng3ftqj7gg-mycluster-default-00000052   184.173.5.49    10.47.84.230      free         normal   Ready   hou02    1.14.8_1538

上記例であれば 184.173.5.49 がパブリック IP アドレスとなっていることが確認できました。つまりこの例であれば、ここまでの結果から http://184.173.5.49:32686/ にアクセスすることで k8s 環境内にデプロイされた MIT スクラッチにアクセスできる、ということになります。


【動作確認】
上記手順で確認した URL にウェブブラウザでアクセスして動作確認します:

2019111800


動いているようです。


【後作業】
作成した環境(pod, service, deployment)を削除するには以下のコマンドを実行します:
$ kubectl get all (mit-scratch3 の pod, service, deployment が存在していることを確認)

$ kubectl delete deployment mit-scratch3 (deployment を削除)

$ kubectl delete service mit-scratch3 (service を削除)

$ kubectl get all (mit-scratch3 の pod, service, deployment が削除されたことを確認)




Windows 10 で kubernates の開発環境を構築する際の方法の1つとして、以下のケースで構築する場合のメモです:
・Kubernetes 本体は minikube を使って Windows 10 に導入
・kubectl は WSL から利用


Kubernetes 本体はローカルで動く minikube を使ってシングルノード環境を構築します。この部分は正しく導入できていさえすれば Windows だろうが、Linux だろうが、Mac だろうが、システム環境の違いを意識することはあまりないと思っています。 ただ実際に利用する段になって kubectl コマンドを実行する環境としては Windows よりもより実践に近い Linux を使いたくなります。というわけで、Windows 10 の WSL (今回は Ubuntu を想定)から利用できるようにしました。

なお、この環境構築をする場合のマシンスペックとして、メモリ 8GB だと構築して minikube start した時点でほぼメモリを使い尽くしてしまいます。動作確認するだけならいいのですが、本格的に開発して動作確認してテストして・・・という段階まで考えるとやはり 16GB くらいはほしいところです。


【前提条件】
- Windows 10
- WSL(Ubuntu 18.0.4) 導入済み


【VirtualBox のインストール】
Windows 10 で minikube を動かす場合、仮想環境のハイパーバイザーが必要です。今回は VirtualBoxを使います。VirtualBox 自体のインストールはごく普通に公式ページから Windows 版をダウンロードし、デフォルト設定のままインストールすればOKです。


【Windows 版 minikube のインストール】
minikube の Github ページから、Windows/amd64 版の minikube 単体をダウンロードします:
2019090801


ダウンロードしたファイルは minikube-windows-amd64.exe というファイル名になっていますが、これを minikube.exe とリネームしてファイルシステムの適当なフォルダに保存します(以下は C:\MyApps\minikube\ というフォルダを作成して、その中に C:\MyApps\minikube\minikube.exe という名前で保存しているものと仮定して以下を続けます)。

このフォルダに PATH を通します。Windows + S キーを押して検索ボックスに「システムの詳細設定」と入力すると「システムの詳細設定の表示」が見つかるので、これを選択します:
2019090802


システムのプロパティウィンドウが表示されたら、「詳細設定」タブを選択し、「環境変数」ボタンをクリック:
2019090803


ユーザー環境変数の Path を選択してから「編集」ボタンをクリック:
2019090804


そして「新規」に minikube.exe が保存されたフォルダ名を追加して、最後に OK ボタンをクリック。これで minikube.exe に PATH が通りました:
2019090805


念の為、動作確認してみます。コマンドプロンプトを起動して、 "minikube version" と入力して実行します。minikube のバージョン番号が表示されれば、ここまでの手順は OK です:
2019090801


【minikube の起動】
コマンドプロンプトから "minikube start" と入力して、minikube を起動します。自動的に必要な VM イメージを見つけて(初回はダウンロードして)自動構築します。通常は minikube が kubectl を見つけてくれるのですが、この方法だと(Windows 版 kubectl を使わない&インストールしていないので)見つからない、という警告が表示されますが、今回は WSL 側に kubectl を用意するので無視します:
2019090802


【WSL に kubectl をインストール】
WSL を起動し、以下のコマンドを入力して kubectl を(/usr/local/bin/ 以下に)導入します:
$ curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl

$ chmod 755 kubectl

$ sudo mv kubectl /usr/local/bin

最後に "kubectl version" を実行して動作確認します(初回は Client Version が表示されれば OK です):
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.3", GitCommit:"2d3c76f9091b6bec110a5e63777c332469e0cba2", GitTreeState:"clean", BuildDate:"2019-08-19T11:13:54Z", GoVersion:"go1.12.9", Compiler:"gc", Platform:"linux/amd64"}

【minikube のエンドポイント情報の取得】
コマンドプロンプトに戻って、"minikube status" を実行し、minikube のエンドポイント情報を確認します。以下の例では 192.168.99.100 というアドレスが表示されていますが、これにポート番号 8443 を加えたもの(192.168.99.100:8443)がエンドポイントとなり、以下で利用することになります:
2019090803


【kubectl の設定】
上記で取得したエンドポイント情報を使って kubectl の設定を行います。WSL から以下のコマンドを順次実行します。なお以下で <コマンドプロンプト実行時のユーザー名> と表示されている部分にはコマンドプロンプト実行時のユーザー名がフォルダ名の一部になっているのでそれをそのまま指定します(上記の画像例の場合であれば、ここには KEIKIMURA が入ります)、また 192.168.99.100:8443 部分は上記で取得したエンドポイント情報です:
$ kubectl config set-credentials minikube --client-certificate=/mnt/c/Users/<コマンドプロンプト実行時のユーザー名>/.minikube/client.crt --client-key=/mnt/c/Users/<コマンドプロンプト実行時のユーザー名>/.minikube/client.key

$ kubectl config set-cluster minikube --server=https://192.168.99.100:8443 --certificate-authority=/mnt/c/Users/<コマンドプロンプト実行時のユーザー名>/.minikube/ca.crt

$ kubectl config set-context minikube --user=minikube --cluster=minikube

$ kubectl config use-context minikube

最後に WSL で "kubectl cluster-info" コマンドを実行して動作を確認し、エンドポイントで正しくクラスタ状態が取得できれば kubectl の設定完了です。シングルノードの kubernetes (kubectl) がローカルの WSL から使えるようになりました:
$ kubectl cluster-info

Kubernetes master is running at https://192.168.99.100:8443
KubeDNS is running at https://192.168.99.100:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.



(参考)
https://dokupe.hatenablog.com/entry/20180408/1523116886

これらの記事の続きです:
IBM Cloud の無料版 Kubernetes サービスを使ってみた(1)
IBM Cloud の無料版 Kubernetes サービスを使ってみた(2)


IBM Cloud の Kubernetes サービスを使って、無料のワーカーノードにアプリケーションをデプロイして、(いったん)削除する所までを紹介しました。最終回である今回は実際にアプリケーションにアクセスしたり、デプロイしたアプリケーションをスケールさせてみたり、外部(インターネット)に公開してみたりします。

今回の作業も基本的にすべて Web Terminal から行います。というわけで、まず Web Terminal を起動します:
2019041109


改めてアプリケーションをデプロイします。が、今回は後述の RC(ReplicatoinController) としてデプロイするため、デプロイ用のファイル(rc.yaml)を作成します:
apiVersion: v1
kind: ReplicationController
metadata:
  name: kubernetes-bootcamp
spec:
  replicas: 1
  selector:
    app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: myk8x
        image: gcr.io/google-samples/kubernetes-bootcamp:v1
        ports:
        - containerPort: 8080

デプロイ時にはこのファイル(rc.yaml)を指定して、以下のコマンドを実行します:
$ kubectl create -f rc.yaml
replicationcontroller/kubernetes-bootcamp created

このコマンドで前回とほぼ同様に kubernetes-bootcamp アプリケーションを1つのポッドにデプロイします。また app: web というセレクターを指定していますが、この後で使います。

この時点で kubernetes-bootcamp アプリケーションが1つのポッドにデプロイされます。一応ポッドと、そして(詳しくは後述の)サービスの状態を確認しておきます(緑字はコメント):
$ kubectl get pod
NAME                        READY   STATUS    RESTARTS   AGE
kubernetes-bootcamp-jfswt   1/1     Running   0          41s  1ポッド

$ kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   172.21.0.1   <none>        443/TCP   3d21h  ここはクラスタサービスなので無視、つまり起動したサービスなし

この時点で1つのポッドにアプリケーションがデプロイされたことが確認できました。しかしこのポッドはまだコンテナの外部からアクセスすることができません。コンテナの外部からでもアクセスできるように紐付けるサービスを作成します。

作成するサービスのための設定ファイル(service.json)を以下の内容で用意します:
{
    "kind": "Service",
    "apiVersion": "v1",
    "metadata": {
        "name": "my-service"
    },
    "spec": {
        "selector": {
            "app": "web"
        },
        "ports": [
            {
                "protocol": "TCP",
                "port": 80,
                "targetPort": 8080,
                "nodePort": 30000
            }
        ],
        "type" : "NodePort"
    }
}

"spec" 内で app:web のラベルをセレクターに指定しています。これが先程作成したデプロイメント時の指定内容となっていて、kubernetes-bootcamp に紐づく形になります。また 8080 番ポートをターゲットに接続し、80 番ポートで公開する、という指定も行い、"my-service" という名前でサービスを作るよう指示しています。

ではこのファイル(service.json)を使ってサービスを作成します。同時に再びポッドとサービスの内容を照会してみます:
$ kubectl create -f service.json
service/my-service created

$ kubectl get pod
NAME                        READY   STATUS    RESTARTS   AGE
kubernetes-bootcamp-jfswt   1/1     Running   0          13m  1ポッド

$ kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   172.21.0.1   <none>        443/TCP   3d21h
my-service   NodePort    172.21.30.70 <none>        80:30000/TCP   94s 追加されたサービス

サービスの追加に成功しました。またその追加されたサービスはクラスター内の内部 IP アドレスである 172.21.30.70:80 上で稼働していることがわかりました。

ではこのサービスを使ってアプリケーションにアクセスしてします:
$ curl http://172.21.30.70/
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-jfswt | v=1

アプリケーションにコンテナ外部からアクセスでき、正しく動作していることも確認できました。ただし、この時点ではまだあくまで内部ネットワーク内からのアクセスが確認できただけで、まだインターネットからはアクセスできません(後述します)。


コンテナの外部からアプリケーションにアクセスできることは確認できましたが、せっかくなので Kubernetes っぽい挙動もさせてみます。現在1つのポッドで動作しているこのアプリケーションをスケールアウトして複数の(今回は2つの)ポッドで起動するようにしてみます:
$ kubectl scale -f rc.yaml --replicas=2
replicationcontroller/kubernetes-bootcamp scaled

この状態で再びポッドとサービスの状態を確認します。ポッドが2つになっていることがわかります。(一方、サービスは何も変わっていません):
$ kubectl get pod
NAME                        READY   STATUS    RESTARTS   AGE
kubernetes-bootcamp-kqkhp   1/1     Running   0          14s スケールアウトして新しく増えたポッド
kubernetes-bootcamp-jfswt   1/1     Running   0          109s

$ kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   172.21.0.1      <none>        443/TCP        3d23h
my-service   NodePort    172.21.30.70    <none>        80:30000/TCP   5m16s

ではこのアプリケーションをインターネットに公開してみます。以下のコマンドを実行します:
$ kubectl expose rc kubernetes-bootcamp --type=NodePort --name=web
service/web exposed

$ kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
kubernetes   ClusterIP   172.21.0.1       <none>        443/TCP          4d20h
my-service   NodePort    172.21.199.105   <none>        80:30000/TCP     4m30s
web          NodePort    172.21.64.253    <none>        8080:30703/TCP   4m9s

"web" という名称のサービスが追加され、kubernetes-bootcamp の rc が expose(外部公開)されました。公開先のポート番号が 30703 になっていることも確認できます。

また、公開先の IP アドレスはワーカーノードのパブリック IP アドレスになっています。ダッシュボードから確認するか、または以下のコマンドを実行します:
2019041601
$ ibmcloud cs workers mycluster (最後のパラメーターはクラスタ名)

ID                                                 Public IP       Private IP       Machine Type   State    Status   Zone    Version   
kube-mel01-pa351ddd2ea19441a689a49159389f9d45-w1   168.1.149.201   10.118.243.103   free           normal   Ready    mel01   1.12.7_1548*


この例だと、パブリック IP アドレスが 168.1.149.201 となっているので、この 30703 番ポートに対してウェブブラウザでアクセスし、期待通りの結果になることを確認します:
2019041602


最後に、サービスとポッドを削除する場合は以下のコマンドを実行します:
$ kubectl delete svc web
service "web" deleted

$ kubectl delete -f service.json
service "my-service" deleted

$ kubectl delete -f rc.yaml
replicationcontroller "kubernetes-bootcamp" deleted


IBM Cloud の(無料の)Kubernetes サービスを使って、アプリケーションのデプロイ、スケール、動作確認、ウェブ公開、といった一連の作業ができることが確認できました。すでに Kubernetes 環境を手元に持っているような人であればともかく、これから Kubernetes を使ってみようと思う人にとっては面倒な準備作業も不要で気軽に始められて、スケールやウェブ公開といった一通りの作業ができるところまで使える環境としてとても便利だと思いました。





このページのトップヘ