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

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

タグ:kubernetes

Docker Desktop ショックがあって以来、なるべく少ない制約の下で docker を動かせる環境を色々調べています。そんな中で見つけた1つの方法が Docker in Docker(以下、DinD)です。

DinD はその名前の通りで、コンテナクラスタ(親)の中で動くコンテナ(子)として Docker サーバー&クライアントを動かす、というものです。多くの場合、「親コンテナ=Kubernetes」となることが多いので、正確には "Docker in Kubernetes" と表現すべきかもしれませんが、広い意味(?)での "Docker in Docker" ということだと思います。

単なる運用環境の観点だと「Kubernetes が使えるなら Docker は要らないのでは?」と思うかもしれませんが、特に開発段階だとコマンドとしての docker CLI を使いたいことがあったり、プロダクション環境とは別の小さな開発環境を Docker で作っておけると便利なことも多くあります。そういった意味で Kubernetes があってもそれとは別に Docker 環境が欲しくなることがあるのでした。


IBM Cloud からも IKS(IBM Kubernetes Services)ROKS(Redhat Openshift Kubernetes Services) が提供されていて、これらの環境でも DinD を使うことができます。特に今回は IKS の 30 日無料版を使って DinD 環境を作って使う手順を紹介します。なお、IKS 30 日無料版の制約事項や環境準備手順についてはこちらの過去記事を参照ください:
http://dotnsf.blog.jp/archives/1079287640.html


↑この記事最後の "$ kubectl get all" コマンドが成功するまでになれば準備完了です。なお Kubernetes クラスタに対して "$ kubectl get all" コマンドが実行できるようになっていれば IKS 以外の他の Kubernetes クラスタ環境でも以下同様にして DinD 環境を作ることができると思います。


【(IKS に) DinD の Pod を作る】
では早速 DinD 環境を作ります。DinD 環境といっても大それたものではなく、コンテナ的に言えば「DinD の Pod を1つ作る」ことになります。そしてその1つの Pod の中に「Docker デーモンのコンテナ」と「Docker クライアントのコンテナ」を1つずつ作ります(つまり1つの Pod の中で2つのコンテナを動かします)。

実際の作成に関しても、以下の内容のマニフェストファイルを用意するだけです(この内容を dind.yml という名前で保存してください):
apiVersion: v1
kind: Pod
metadata:
  name: dind
spec:
  containers:
    - name: docker
      image: docker:19.03
      command: ["docker", "run", "nginx:latest"]
      env:
        - name: DOCKER_HOST
          value: tcp://localhost:2375
    - name: dind-daemon
      image: docker:19.03-dind
      env:
        - name: DOCKER_TLS_CERTDIR
          value: ""
      resources:
        requests:
          cpu: 20m
          memory: 512Mi
      securityContext:
        privileged: true

そして、kubectl コマンドで以下を実行してマニフェストを適用します:
$ kubectl apply -f dind.yml

※または dind.yml を用意しなくても、このコマンドでも同じ結果になります:
$ kubectl apply -f https://raw.githubusercontent.com/dotnsf/dind_iks/main/dind.yml


初回のみイメージのダウンロードで少し時間がかかりますが、しばらく待つと dind という名前の Pod が1つ(コンテナは docker dind-daemon の2つ)起動します:
$ kubectl get pods

NAME READY STATUS RESTARTS AGE dind 2/2 Running 2 101m $ kubectl describe pod dind Name: dind Namespace: default Priority: 0 Node: 10.144.222.147/10.144.222.147 Start Time: Sun, 20 Feb 2022 21:52:46 +0900 Labels: Annotations: cni.projectcalico.org/containerID: 0c110243107d25c9e27b82202871504047d9ac691ad294d8f89bb1a9b114ca5e cni.projectcalico.org/podIP: 172.30.216.78/32 cni.projectcalico.org/podIPs: 172.30.216.78/32 kubernetes.io/psp: ibm-privileged-psp Status: Running IP: 172.30.216.78 IPs: IP: 172.30.216.78 Containers: docker: Container ID: containerd://0dbd05bc4171f96caaa7601a10e1b2f853511b4ed7087ab3958c016024c65f1c Image: docker:19.03 Image ID: docker.io/library/docker@sha256:ea1f0761c92b600417ad14bc9b2b3a30abf8e96e94895fee6cbb5353316f30b0 Port: Host Port: Command: docker run nginx:latest State: Running Started: Sun, 20 Feb 2022 22:53:02 +0900 Last State: Terminated Reason: Completed Exit Code: 0 Started: Sun, 20 Feb 2022 21:53:08 +0900 Finished: Sun, 20 Feb 2022 22:53:01 +0900 Ready: True Restart Count: 2 Environment: DOCKER_HOST: tcp://localhost:2375 Mounts: /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-gz5fb (ro) dind-daemon: Container ID: containerd://5a6a15eefa9ebdf8a0dcc825f1c87fa4a5a37daab005a1d83e5df8f9a33ff7bb Image: docker:19.03-dind Image ID: docker.io/library/docker@sha256:c85365ad08c7f6e02ac962a8759c4a5b8512ea5c294d3bb9ed25fca52e9e22e5 Port: Host Port: State: Running Started: Sun, 20 Feb 2022 21:53:07 +0900 Ready: True Restart Count: 0 Requests: cpu: 20m memory: 512Mi Environment: DOCKER_TLS_CERTDIR: Mounts: /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-gz5fb (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: kube-api-access-gz5fb: Type: Projected (a volume that contains injected data from multiple sources) TokenExpirationSeconds: 3607 ConfigMapName: kube-root-ca.crt ConfigMapOptional: DownwardAPI: true QoS Class: Burstable Node-Selectors: Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 600s node.kubernetes.io/unreachable:NoExecute op=Exists for 600s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Created 43m (x3 over 103m) kubelet Created container docker Normal Started 43m (x3 over 103m) kubelet Started container docker Normal Pulled 43m (x2 over 103m) kubelet Container image "docker:19.03" already present on machine d


DinD 環境はあっけなく完成しました。


【(IKS の) DinD 内の docker を操作する】
次に完成した DinD 環境を実際に CLI で使ってみます。そのためにまずは Docker クライアントが使える環境のシェルにアタッチする必要があります。Docker クライアントは docker という名前のコンテナで動いていることが分かっているので、以下のコマンドを実行します:
$ kubectl exec -it dind -c docker -- /bin/sh

/ #

プロンプトが "/ #" という記号に変わればアタッチ成功です。ここからは docker CLI コマンドが実行できます。試しに "docker version" コマンドを実行するとクライアント&サーバー双方の docker バージョン情報(下の例ではどちらも "19.03.15")を確認できます:
/ # docker version

Client: Docker Engine - Community
 Version:           19.03.15
 API version:       1.40
 Go version:        go1.13.15
 Git commit:        99e3ed8
 Built:             Sat Jan 30 03:11:43 2021
 OS/Arch:           linux/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.15
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       99e3ed8
  Built:            Sat Jan 30 03:18:13 2021
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          v1.3.9
  GitCommit:        ea765aba0d05254012b0b9e595e995c09186427f
 runc:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683


実際にサーバーイメージをデプロイして動作確認してみましょう。というわけで、まずは nginx イメージを 8000 番ポートでデプロイしてみます:
/ # docker run -d --name ningx -p 8000:80 nginx

成功したら早速アクセスして動作確認を・・・と思ったのですが、この docker コンテナには HTTP クライアントが curl 含めてインストールされていないようでした。というわけで動作確認用のコマンドもインストールしておきます。とりあえず curl と w3m あたりでいいですかね。。:
/ # apk add --update curl

/ # apk add --update w3m

インストールが成功したら、まずは curl で http://localhost:8000/ にアクセスしてみます:
/ # curl http://localhost:8000/

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

うぉっ、と。少なくとも HTTP サーバーとして動いているらしいことは確認できたのですが、これだとちょっと見にくいですね。というわけで、先程一緒にインストールした w3m で確認してみます:
/ # w3m http://localhost:8000/

Welcome to nginx!

If you see this page, the nginx web server is successfully installed and working. Further configuration is required.

For online documentation and support please refer to nginx.org.
Commercial support is available at nginx.com.

Thank you for using nginx.

2022022101


かろうじて Nginx のトップ画面らしきものが確認できました。というわけで一応動いていると思います。


【まとめ】
というわけで DinD が IBM Cloud の 30 日無料版 Kubernetes クラスタ環境でも動かせることが確認できました。

とはいえ、もともとは Docker Desktop の代替になるような Docker 環境を探していたことに立ち返ると、この(クライアント側のような) CLI だけの Docker 環境はどうしても使い道が限られてしまうように感じます。ある程度、Docker を理解している人向けに、CLI だけで完結する使いみちであればなんとか、といったところでしょうか。


本当はここで作った Pod を外部に EXPOSE できるといいんですが、自分で試行錯誤している限りではまだうまく行ってません。もし方法をご存じの方がいらっしゃったら是非教えてください。


(2022/02/21 追記ここから)
試行錯誤の中で外部公開する方法がわかりました。

上述の "$ kubectl apply -f ..." コマンドを実行する箇所の内容を以下のように変更してください:
$ kubectl apply -f https://raw.githubusercontent.com/dotnsf/dind_iks/main/dind_expose.yml

なお、ここで指定している dind_expose.yml の内容は以下のようなものです。Pod を Deployment に書き換えた上で Service オブジェクトを追加して、8000 番ポートでの待受けを 30800 番ポートから転送するように(この後の作業で 8000 番ポートで待ち受けるアプリケーションをデプロイする想定で)あらかじめ公開しています:
apiVersion: v1
kind: Service
metadata:
  name: dind
spec:
  selector:
    app: dind
  ports:
  - port: 8000
    name: port8000
    protocol: TCP
    targetPort: 8000
    nodePort: 30800
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dind
spec:
  selector:
    matchLabels:
      app: dind
  replicas: 1
  template:
    metadata:
      labels:
        app: dind
    spec:
      containers:
        - name: docker
          image: docker:19.03
          command: ["docker", "run", "nginx:latest"]
          env:
            - name: DOCKER_HOST
              value: tcp://localhost:2375
        - name: dind-daemon
          image: docker:19.03-dind
          env:
            - name: DOCKER_TLS_CERTDIR
              value: ""
          resources:
            requests:
              cpu: 20m
              memory: 512Mi
          securityContext:
            privileged: true

このコマンドの後、"$ kubectl get all" を実行すると以下のような結果になります:
$ kubectl get all
NAME READY STATUS RESTARTS AGE pod/dind-7d8546bc8-fxbhw 2/2 Running 1 19s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/dind NodePort 172.21.8.233 8000:30800/TCP 21s service/kubernetes ClusterIP 172.21.0.1 443/TCP 3d10h NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/dind 1/1 1 1 20s NAME DESIRED CURRENT READY AGE replicaset.apps/dind-7d8546bc8 1 1 1 21s

以前の方法では Pod 名は "dind" 固定だったのですが、ここでは "dind-" に続けてランダムな文字列が付与されています(上例では "dind-7d8546bc8-fxbhw" となっています)。この Pod 名を指定して以下を実行して docker コンテナのシェルに接続します:
$ kubectl exec -it dind-7d8546bc8-fxbhw -c docker -- /bin/sh

/ #

この後は以前の方法と同様にして docker コンテナ内で NGINX を 8000 番ポートで起動します:
/ # docker run -d --name ningx -p 8000:80 nginx

これで以前と同様に k8s のワーカーノード内で(NodePort サービスを使って) NGINX が 8000 番ポートで起動します。ただ今回は Service オブジェクトで 8000 番リクエストを 30800 番ポートで外部公開しているので、 http://(ワーカーノードのパブリックIPアドレス):30800/ にアクセスすればクラスタ外部からでもこの NGINX に接続できるようになっています。


ワーカーノードのパブリック IP アドレスは IBM Cloud の IKS 環境であれば、IBM Cloud ダッシュボード画面から確認することができます(この例では "169.51.206.71" となっています):
2022022102


というわけで、改めてウェブブラウザで http://169.51.206.71:30800/ にアクセスしてみると、、、期待通りの画面が表示されました! IBM Cloud の30日無料版 Kubernetes クラスタ環境で構築した DinD のコンテナを外部に公開することができることが確認できました:
2022022100


というわけで、30 日間無料の IBM Cloud Kubernetes 環境を使って、Docker および Kubernetes クラスタの実行環境を構築することができました。

(2022/02/21 追記ここまで)
 

まず最初に、自分がもともと今回勝利する技術に興味を持ったきっかけは GIGAZINE に掲載されたこのネタでした:


これに近いことを自分でもやろうとするとどうすればよいか、、と考えました。Kubernetes (以下 "k8s")には API サーバーが存在していて、kubectl はこの API サーバー経由でリクエスト/レスポンスを使って動く、ということは知っていたので、うまくやれば REST API でなんとかなるんじゃないか、、、という発想から色々調べてみたのでした。

調べてみるとわかるのですが、k8s の API サーバーを直接利用するには kubectl コマンドの裏で行われている認証などの面倒な部分も意識する必要がありました:
k8s_api



本ブログエントリでは、認証などの面倒な指定を回避して利用できるよう k8s の proxy 機能を使って k8s クラスタの外部から k8s API を実行してクラスタの状態を確認できる様子を紹介します。 なおここで紹介する k8s の環境としては IBM Cloud の 30 日無料 k8s クラスタ環境を使って紹介します。


【k8s クラスタの準備】
まずは k8s クラスタを用意します。手元で用意できる環境があればそれを使ってもいいのですが、ここでは IBM Cloud から提供されている 30 日間無料のシングルワーカーノードクラスタ環境を使うことにします。この環境を入手するまでの具体的な手順については以前に記載したこちらの別エントリを参照してください:


次にこのシングルワーカーノードクラスタ環境をカレントコンテキストにして kubectl から利用できる状態にします。

まず、今回紹介する機能は CLI コマンドを使います。最低限必要なのは kubectl コマンドで、こちらはまだインストールできていない場合はこちらなどを参考にインストールしてください。また IBM Cloud の k8s を使う場合は kubectl に加えて ibmcloud コマンドも必要です。ibmcloud コマンドのインストールについてはこちらのページ等を参照して、自分の環境に合わせた ibmcloud コマンドをインストールしてください:


CLI の用意ができたら IBM Cloud のダッシュボード画面から作成したクラスタを選択し、画面上部の「ヘルプ」と書かれた箇所をクリックします:
2022011401


すると画面右側にヘルプメニューが表示されます。ここで「クラスターへのログイン」と書かれた箇所を展開して、その中に書かれている2つのコマンド(ログインコマンドとカレントコンテキストを指定するコマンド)をターミナルなどから続けて実行することで、CLI からも IBM Cloud にログインし、k8s クラスタをカレントコンテキストに切り替えることができます:
2022011402


$ ibmcloud login

$ ibmcloud ks cluster config -c XXXX(一意のクラスタID)XXXX

ここまで実行することで CLI 環境のカレントコンテキストが IBM Cloud 上の k8s になり、kubectl コマンドも IBM Cloud の k8s に対して実行できるようになります(以下は実行例です):
$ kubectl get all

NAME                            READY   STATUS      RESTARTS   AGE
pod/hostname-7b4f76fd59-c8b2l   1/1     Running     0          4d22h

NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
service/hostname     NodePort    172.21.105.49           8080:30080/TCP   4d22h
service/kubernetes   ClusterIP   172.21.0.1              443/TCP          4d23h

NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/hostname   1/1     1            1           4d22h

NAME                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/hostname-7b4f76fd59   1         1         1       4d22h

k8s 環境の準備段階としてここまでが必要です。IBM Cloud 以外の k8s クラスタ環境を利用する場合でもここまでと同様の準備ができていれば、以下の Proxy 実行から続けて行うことができるはずです。


【k8s Proxy の起動】
この状態で k8s Proxy を起動します:
$ kubectl proxy
Starting to serve on 127.0.0.1:8001

↑8001 番ポートで Proxy が起動した状態になります。Proxy を終了するにはこの画面で Ctrl+C を押します。

ここまでできていれば localhost:8001 を経由して k8s REST API を実行し、その結果を参照することができるようになっています。


【k8s REST API の実行】
いくつか k8s REST API を実行してみることにします。今回は(別のターミナルから)curl コマンドを使って REST API を実行してみます。

例えばクラスタ上で稼働している Pods の一覧を取得してみます。kubectl であれば
$ kubectl get pods

NAME                        READY   STATUS    RESTARTS   AGE
hostname-7b4f76fd59-c8b2l   1/1     Running   0          4d22h
というコマンドを実行するところです。今回は上記のように hostname という名前の Pod が1つだけ存在している状態であるとします。

一方、REST API ではバージョンや namespace を指定して以下のように実行します。また結果もこのように取得できます:
$ curl -X GET http://localhost:8001/api/v1/namespaces/default/pods

{
  "kind": "PodList",
  "apiVersion": "v1",
  "metadata": {
    "resourceVersion": "92526"
  },
  "items": [
    {
      "metadata": {
        "name": "hostname-7b4f76fd59-c8b2l",
        "generateName": "hostname-7b4f76fd59-",
        "namespace": "default",
        "uid": "3753ac40-6d2c-428a-86ac-4181dfc0cce9",
        "resourceVersion": "2162",
        "creationTimestamp": "2022-01-09T07:27:25Z",
        "labels": {
          "app": "hostname",
          "pod-template-hash": "7b4f76fd59"
        },
        "annotations": {
          "cni.projectcalico.org/containerID": "1e1592caee1c1a76426c1fca88a70ddb681fae650301cd0cbe3985f0b0975d45",
          "cni.projectcalico.org/podIP": "172.30.153.142/32",
          "cni.projectcalico.org/podIPs": "172.30.153.142/32",
          "kubernetes.io/psp": "ibm-privileged-psp"
        },
        "ownerReferences": [
          {
            "apiVersion": "apps/v1",
            "kind": "ReplicaSet",
            "name": "hostname-7b4f76fd59",
            "uid": "9160ac38-3e4e-4a01-80a1-affba620fa9c",
            "controller": true,
            "blockOwnerDeletion": true
          }
        ],
        "managedFields": [
          {
            "manager": "kube-controller-manager",
            "operation": "Update",
            "apiVersion": "v1",
            "time": "2022-01-09T07:27:25Z",
            "fieldsType": "FieldsV1",
            "fieldsV1": {"f:metadata":{"f:generateName":{},"f:labels":{".":{},"f:app":{},"f:pod-template-hash":{}},"f:ownerReferences":{".":{},"k:{\"uid\":\"9160ac38-3e4e-4a01-80a1-affba620fa9c\"}":{".":{},"f:apiVersion":{},"f:blockOwnerDeletion":{},"f:controller":{},"f:kind":{},"f:name":{},"f:uid":{}}}},"f:spec":{"f:containers":{"k:{\"name\":\"hostname\"}":{".":{},"f:image":{},"f:imagePullPolicy":{},"f:name":{},"f:ports":{".":{},"k:{\"containerPort\":8080,\"protocol\":\"TCP\"}":{".":{},"f:containerPort":{},"f:protocol":{}}},"f:resources":{},"f:terminationMessagePath":{},"f:terminationMessagePolicy":{}}},"f:dnsPolicy":{},"f:enableServiceLinks":{},"f:restartPolicy":{},"f:schedulerName":{},"f:securityContext":{},"f:terminationGracePeriodSeconds":{}}}
          },
          {
            "manager": "calico",
            "operation": "Update",
            "apiVersion": "v1",
            "time": "2022-01-09T07:27:26Z",
            "fieldsType": "FieldsV1",
            "fieldsV1": {"f:metadata":{"f:annotations":{"f:cni.projectcalico.org/containerID":{},"f:cni.projectcalico.org/podIP":{},"f:cni.projectcalico.org/podIPs":{}}}}
          },
          {
            "manager": "kubelet",
            "operation": "Update",
            "apiVersion": "v1",
            "time": "2022-01-09T07:27:36Z",
            "fieldsType": "FieldsV1",
            "fieldsV1": {"f:status":{"f:conditions":{"k:{\"type\":\"ContainersReady\"}":{".":{},"f:lastProbeTime":{},"f:lastTransitionTime":{},"f:status":{},"f:type":{}},"k:{\"type\":\"Initialized\"}":{".":{},"f:lastProbeTime":{},"f:lastTransitionTime":{},"f:status":{},"f:type":{}},"k:{\"type\":\"Ready\"}":{".":{},"f:lastProbeTime":{},"f:lastTransitionTime":{},"f:status":{},"f:type":{}}},"f:containerStatuses":{},"f:hostIP":{},"f:phase":{},"f:podIP":{},"f:podIPs":{".":{},"k:{\"ip\":\"172.30.153.142\"}":{".":{},"f:ip":{}}},"f:startTime":{}}}
          }
        ]
      },
      "spec": {
        "volumes": [
          {
            "name": "kube-api-access-4qr4h",
            "projected": {
              "sources": [
                {
                  "serviceAccountToken": {
                    "expirationSeconds": 3607,
                    "path": "token"
                  }
                },
                {
                  "configMap": {
                    "name": "kube-root-ca.crt",
                    "items": [
                      {
                        "key": "ca.crt",
                        "path": "ca.crt"
                      }
                    ]
                  }
                },
                {
                  "downwardAPI": {
                    "items": [
                      {
                        "path": "namespace",
                        "fieldRef": {
                          "apiVersion": "v1",
                          "fieldPath": "metadata.namespace"
                        }
                      }
                    ]
                  }
                }
              ],
              "defaultMode": 420
            }
          }
        ],
        "containers": [
          {
            "name": "hostname",
            "image": "dotnsf/hostname",
            "ports": [
              {
                "containerPort": 8080,
                "protocol": "TCP"
              }
            ],
            "resources": {

            },
            "volumeMounts": [
              {
                "name": "kube-api-access-4qr4h",
                "readOnly": true,
                "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
              }
            ],
            "terminationMessagePath": "/dev/termination-log",
            "terminationMessagePolicy": "File",
            "imagePullPolicy": "Always"
          }
        ],
        "restartPolicy": "Always",
        "terminationGracePeriodSeconds": 30,
        "dnsPolicy": "ClusterFirst",
        "serviceAccountName": "default",
        "serviceAccount": "default",
        "nodeName": "10.144.214.171",
        "securityContext": {

        },
        "imagePullSecrets": [
          {
            "name": "all-icr-io"
          }
        ],
        "schedulerName": "default-scheduler",
        "tolerations": [
          {
            "key": "node.kubernetes.io/not-ready",
            "operator": "Exists",
            "effect": "NoExecute",
            "tolerationSeconds": 600
          },
          {
            "key": "node.kubernetes.io/unreachable",
            "operator": "Exists",
            "effect": "NoExecute",
            "tolerationSeconds": 600
          }
        ],
        "priority": 0,
        "enableServiceLinks": true,
        "preemptionPolicy": "PreemptLowerPriority"
      },
      "status": {
        "phase": "Running",
        "conditions": [
          {
            "type": "Initialized",
            "status": "True",
            "lastProbeTime": null,
            "lastTransitionTime": "2022-01-09T07:27:25Z"
          },
          {
            "type": "Ready",
            "status": "True",
            "lastProbeTime": null,
            "lastTransitionTime": "2022-01-09T07:27:36Z"
          },
          {
            "type": "ContainersReady",
            "status": "True",
            "lastProbeTime": null,
            "lastTransitionTime": "2022-01-09T07:27:36Z"
          },
          {
            "type": "PodScheduled",
            "status": "True",
            "lastProbeTime": null,
            "lastTransitionTime": "2022-01-09T07:27:25Z"
          }
        ],
        "hostIP": "10.144.214.171",
        "podIP": "172.30.153.142",
        "podIPs": [
          {
            "ip": "172.30.153.142"
          }
        ],
        "startTime": "2022-01-09T07:27:25Z",
        "containerStatuses": [
          {
            "name": "hostname",
            "state": {
              "running": {
                "startedAt": "2022-01-09T07:27:36Z"
              }
            },
            "lastState": {

            },
            "ready": true,
            "restartCount": 0,
            "image": "docker.io/dotnsf/hostname:latest",
            "imageID": "docker.io/dotnsf/hostname@sha256:e96808f33e747004d895d8079bc05d0e98010114b054aea825a6c0b1573c759e",
            "containerID": "containerd://7cc6b4eafd0723f46f78fc933a43ddefc6d4ddc75796608548b34a9aaae77f17",
            "started": true
          }
        ],
        "qosClass": "BestEffort"
      }
    }
  ]
}

次に API で Pod を作成してみます。Pod そのものはシンプルな Hello World 出力コンテナとして、この Pod を API で作成するには以下のように指定します(YAML 部分をファイルで指定する方法がよくわからないので、ご存じの人がいたら教えてください):
$ curl -X POST -H 'Content-Type: application/yaml' http://localhost:8001/api/v1/namespaces/default/pods -d '
apiVersion: v1
kind: Pod
metadata:
  name: pod-example
spec:
  containers:
  - name: ubuntu
    image: ubuntu:trusty
    command: ["echo"]
    args: ["Hello World"]
  restartPolicy: Never
'

{
  "kind": "Pod",
  "apiVersion": "v1",
  "metadata": {
    "name": "pod-example",
    "namespace": "default",
    "uid": "79d0f086-6c6f-445a-9819-69ae8630710a",
    "resourceVersion": "92782",
    "creationTimestamp": "2022-01-14T06:25:25Z",
    "annotations": {
      "kubernetes.io/psp": "ibm-privileged-psp"
    },
    "managedFields": [
      {
        "manager": "curl",
        "operation": "Update",
        "apiVersion": "v1",
        "time": "2022-01-14T06:25:25Z",
        "fieldsType": "FieldsV1",
        "fieldsV1": {"f:spec":{"f:containers":{"k:{\"name\":\"ubuntu\"}":{".":{},"f:args":{},"f:command":{},"f:image":{},"f:imagePullPolicy":{},"f:name":{},"f:resources":{},"f:terminationMessagePath":{},"f:terminationMessagePolicy":{}}},"f:dnsPolicy":{},"f:enableServiceLinks":{},"f:restartPolicy":{},"f:schedulerName":{},"f:securityContext":{},"f:terminationGracePeriodSeconds":{}}}
      }
    ]
  },
  "spec": {
    "volumes": [
      {
        "name": "kube-api-access-mc7tz",
        "projected": {
          "sources": [
            {
              "serviceAccountToken": {
                "expirationSeconds": 3607,
                "path": "token"
              }
            },
            {
              "configMap": {
                "name": "kube-root-ca.crt",
                "items": [
                  {
                    "key": "ca.crt",
                    "path": "ca.crt"
                  }
                ]
              }
            },
            {
              "downwardAPI": {
                "items": [
                  {
                    "path": "namespace",
                    "fieldRef": {
                      "apiVersion": "v1",
                      "fieldPath": "metadata.namespace"
                    }
                  }
                ]
              }
            }
          ],
          "defaultMode": 420
        }
      }
    ],
    "containers": [
      {
        "name": "ubuntu",
        "image": "ubuntu:trusty",
        "command": [
          "echo"
        ],
        "args": [
          "Hello World"
        ],
        "resources": {

        },
        "volumeMounts": [
          {
            "name": "kube-api-access-mc7tz",
            "readOnly": true,
            "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
          }
        ],
        "terminationMessagePath": "/dev/termination-log",
        "terminationMessagePolicy": "File",
        "imagePullPolicy": "IfNotPresent"
      }
    ],
    "restartPolicy": "Never",
    "terminationGracePeriodSeconds": 30,
    "dnsPolicy": "ClusterFirst",
    "serviceAccountName": "default",
    "serviceAccount": "default",
    "securityContext": {

    },
    "imagePullSecrets": [
      {
        "name": "all-icr-io"
      }
    ],
    "schedulerName": "default-scheduler",
    "tolerations": [
      {
        "key": "node.kubernetes.io/not-ready",
        "operator": "Exists",
        "effect": "NoExecute",
        "tolerationSeconds": 600
      },
      {
        "key": "node.kubernetes.io/unreachable",
        "operator": "Exists",
        "effect": "NoExecute",
        "tolerationSeconds": 600
      }
    ],
    "priority": 0,
    "enableServiceLinks": true,
    "preemptionPolicy": "PreemptLowerPriority"
  },
  "status": {
    "phase": "Pending",
    "qosClass": "BestEffort"
  }
}

kubectl コマンドで Pods 一覧を見ると、Pods が追加/実行され、終了していることが確認できます:
$ kubectl get pods

NAME                        READY   STATUS      RESTARTS   AGE
hostname-7b4f76fd59-c8b2l   1/1     Running     0          4d23h
pod-example                 0/1     Completed   0          3m


といった感じで、k8s Proxy を使うことで簡単に k8s API を利用できる REST API サーバーを作ることができそうでした。もともとこの技術に興味を持った GIGAZINE に掲載されたこのネタも、REST API の Proxy を経由して API を実行して結果を GUI で視覚化して・・・ というのも、そこまで難しくない気がしています。

なお k8s の REST API についてはここから一覧を参照できます:
https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/


(参照)
https://qiita.com/iaoiui/items/36e86d173e451a7b18be

3月31日に IBM Cloud の新しいサーバーレス環境である Code Engine が正式リリースされました:
2021040215


ベータ公開の段階から利用レポートを見かけることもありましたが、価格も公開された状態で改めて利用してみました。その利用手順含めて紹介します。 なお詳しくは後述しますが、この Code Engine は無料枠を使って利用することも可能ですが、ライトプラン契約では利用できません。利用するにはクレジットカードを登録するなどしてベーシックプランに切り替えて利用する必要がある点に注意してください。

【Code Engine とは】
Knative ベースのサーバーレスエンジンです。実行エンジンそのものがコンテナオーケストレーションである Kubernetes 環境上に構築されており、Kubernetes をほとんど意識することなくイベント駆動型のアプリケーションをデプロイ/管理することができると同時に、Kubernetes による自動スケールも行われることで詳しい設定をすることなく安定した稼働を実現することができます。利用料金は後述しますが、「CPU やメモリを実際につかったぶんだけ」課金されます(使わない場合は無料です、加えて無料枠もあるのである程度の稼働は無料枠内で実現できます)。

エンジン上で動かすアプリケーションはコンテナイメージの URL を指定する形でもできますし、イメージ化されていなくてもソースコードが公開されていれば(Dockerfile を用意することで)コンテナをビルドして動かすことも可能です。


【利用手順】
IBM Cloud にログインし、「リソースの作成」を選択します:
2021040201


サービスのカタログ画面が表示されます:
2021040202


検索バーで "Code Engine" を指定して検索します("Code" くらいまで入力すると検索候補に表示されるので、これを選択します):
2021040203


Code Engine の説明ページが表示されます。実際に利用するには「作成の開始」ボタンをクリックします:
2021040204


作成する対象を選択します。ここでは通常の(利用者がウェブブラウザからアクセス可能な)ウェブアプリケーションを作ることにするので「アプリケーション」を選択します。また必要に応じて名前を指定します(デフォルトのままでも構いません):
2021040205


名前確定後に、プロジェクトを選択または作成します。初回ではまだプロジェクトは存在していないので「プロジェクトの作成」をクリックします:
2021040206


画面右にプロジェクト作成のダイアログが表示されます:
2021040207


このダイアログ内でエンジンを稼働させるロケーション(下図では「東京」)、およびプロジェクトを管理するための名称を指定します。最後に「作成」をクリック:
2021040208


作成したプロジェクトが選択された状態になります。続けてこのサーバーレス環境で実行するコードを指定します。コンテナイメージか、(Docker ファイルのある)ソースコードを指定できますが、ここではコンテナイメージを選択しています。コンテナイメージの場合は実際に公開されているコンテナイメージの URL および稼働ポート番号を指定します:
2021040209


※上の例ではコンテナ URL として docker.io/dotnsf/hostname を指定しています。このイメージは私が作って公開しているもので、「8080 番ポートに HTTP アクセスすると /etc/hostname の内容を表示する」というだけのシンプルなアプリケーションです。負荷少なく動くという意味で動作確認向きなので、よかったら使ってください。


コードの指定に続けてランタイムの設定を行います。稼働インスタンス数や各インスタンスのスペックを指定します(複数インスタンスでの稼働に未対応など、スケールアウトしてほしくない場合はインスタンス最大数を1に設定します)。最後に画面右のアプリケーションの「作成」をクリック:
2021040210


画面が切り替わって、プロジェクトのダッシュボードが表示されます。作成直後は「デプロイ中」というステータスになっているはずです:
2021040211


しばらく待つとコンテナのデプロイが完了し、「準備完了」というステータスに切り替わります。またこうなると画面右側に「アプリケーションの URL を開く」というホットスポットが表示されるので、ここをクリックしてアプリケーションにアクセスします:
2021040212


新しいタブが開いて、Code Engine 内で稼働しているアプリケーションにアクセスできました(このアプリケーションの場合はコンテナの /etc/hostname ファイルの内容が表示されています)。特別な設定はしていませんが、ホスト名が自動的に割り当てられて HTTPS でアクセスできています:
2021040213


実際には Kubernetes 上で動いているため、アプリケーションの負荷に応じた自動スケールなどは、ほぼ意識することなく(上記設定だけで)実現できています。Kubernetes を抽象化した形で利用しているため、非常に使いやすいプラットフォームを実現できています。


なお、この Code Engine の料金は以下のようになっていました(2021/04/03 時点)。CPU 稼働時間、利用メモリ、および HTTP リクエスト数について1ヶ月ごとの無料枠と、無料枠を超えて利用した場合の価格が表示されています。このあたりはプロジェクト作成時のインスタンスリソース指定内容とも関わってくるので、アプリケーションの特性に応じてカスタマイズする余地があると思っていますが、試験的に利用する想定であればかなりお手軽な価格帯のように感じます:
2021040214







 

GitHub が提供をはじめた CI/CD 機能である GitHub Actions を使って IBM Cloud の IKS(IBM Kubernetes Services) にアプリケーションをデプロイできることを確認したので、一連の手順を紹介します。


【何をする?】
簡単に言うと、GitHub のリポジトリにアプリケーションのソースコードをコミット&プッシュすると、その最新コードのアプリケーションが IBM Cloud 内の Kubernetes サービスへ自動デプロイされて公開される、ということを GitHub 内の機能だけで実現します。

なお、GitHub Actions は GitHub の無料アカウントで使える機能であることに加え、IBM Cloud の IKS も(クレジットカード登録が必要な BASIC アカウントに切り替える必要はありますが)Kubernetes のシングルワーカーノードが 30 日間無料で利用できます。 以下に紹介する内容は全て無料で試すことができる内容です。

では以下に IKS の準備、GitHub の準備に続けて実際にアプリケーションコードをコミットして IKS 上で動かす所までの手順を紹介します。


【IKS の準備】
IBM Cloud で IKS サービスを作成します。IBM Cloud にログインします。ライトアカウントの場合は IKS が利用できないため、クレジットカードを登録して BASIC アカウントに切り替えるか、または新たにアカウントを作成して BASIC アカウントに切り替える必要があります。

BASIC アカウントでログイン後、画面右上の「リソースの作成」ボタンをクリックします:
2020052701


作成するサービスを一覧から選択します。画面左メニューから「サービス」-「コンピュート」を選択し、画面右の一覧から "Kubernetes Service(以下 IKS)" を選択します:
2020052702


IKS は有償版と無償版(30日経過後に削除)があります。以下は無償版である「無料クラスター」を選択している想定で紹介を続けます。クラスター名は "mycluster" 、リソースグループは "Default"(いずれも既定値)として画面右の「作成」ボタンをクリックします:
2020052703


ここから IKS 環境の作成が始まります。操作できる状態になるまでしばらく(数10分程度)かかります:
2020052704


この IKS の準備をしている時間を使って、この後の作業で利用する API キーを用意しておきます。画面右上のメニューから「管理」を選び、「アクセス(IAM)」を右クリックしてリンクを新しいタブで開く形で(つまり別ウィンドウや別タブで開く形で)選択します:
2020052706


別ウィンドウでアクセス設定画面が開いたら、画面左メニューで「API キー」を選び、「IBM Cloud API キーの作成」ボタンをクリックします:
2020052707


ダイアログが表示されるので API キーの名称(任意ですが、例えば "API Key for GitHub Actions" など)を入力して「作成」します:
2020052708


正しく実行できると以下のような画面になります。作成された API キーはマスクされ、表示されていません。この画面をこのまま閉じてしまうと作成された API キーの内容を再度確認することはできなくなるため、ファイルでダウンロードしておくか、クリップボードにコピーしておくか、目のアイコンをクリックして、マスクを外して表示し、その内容をどこかに保存しておく必要があります:
2020052709


目のアイコンをクリックするとマスクがはずれ、API キーの値を確認することができます。繰り返しますが、このダイアログを消すと API キーを再確認することはできません。忘れてしまった場合は再度 API キーを新規作成する必要がある点に注意してください。なんらかの方法で API キーの内容を再確認できるようこの内容を保存しておきます(後で使います):
2020052710


あらためて IKS サービスの作成状況を確認しておきましょう。「ファイナライズ中」と出ていればあと少しです。。。:
2020052711


しばらく待って全ての準備が完了すると、下図のように「通常」というステータスに変わります。この状態になっていれば IKS 上にアプリケーションをデプロイするための準備が整ったことになります:
2020052712


この画面を離れる前に Web 端末の準備もしておきます。この IKS で用意する Kubernetes 環境はローカルマシンの kubectl などから操作することも可能ですが、kubectl の環境を持っていない人でもブラウザ画面から利用できるターミナル機能が用意されており、こちらを使うことでローカルインストールや環境設定も不要で利用することができるようになって便利です。

Web 端末を利用するには IKS 画面右上の「Actions...」と書かれた箇所をクリックし、メニューから「Web 端末」を選択します:
2020052705


初めてこの操作をした場合はインストールができていないため以下のようなダイアログが表示されます。「インストール」をクリックして数分お待ち下さい:
2020052713


Web 端末のインストールが出来た後に改めて「Actions...」-「Web 端末」を選択すると、画面内にターミナル機能が現れ、ここから kubectl コマンドや IBM Cloud への命令を実行する ibmcloud コマンドを利用することができるます。画面が小さくて不便な場合は(別タブに)最大化して利用することもできます:
2020052714


この Web 端末を使って IBM Cloud CR(Container Registry) の名前空間をセットアップしておきます(後で使います)。まず "ibmcloud login" と入力します。IBM ID とパスワードの入力が促されるので入力し、Web 端末で IBM Cloud にログインします:
$ ibmcloud login

一度この時点で CR の名前空間一覧を確認しておきます。確認するためのコマンドは "ibmcloud cr namespaces" で、初めて実行した場合は1つも定義されていないという結果となるはずです:
$ ibmcloud cr namespaces

改めて名前空間を作成します。ユニークな名前空間名を指定して "ibmcloud cr namespace-add (名前空間)" を実行します:
$ ibmcloud cr namespace-add (名前空間名)

名前空間名は他の人が使っているものを指定することはできません(エラーとなります)。自分の場合は "ibmcloud cr namespace dotnsf-ns" と入力しました。実際に実行する場合はここで他の人が使っていないものを指定して実行してください(エラーとなる場合はエラーにならずに実行完了するまで繰り返して実行してください)。

最後に CR の名前一覧表示コマンドを再度実行し、直前のコマンドで作成した名前空間が一覧に含まれていることを確認してください:
$ ibmcloud cr namespaces

自分の上記例ではこんな感じになりました。この名前空間名も後で利用します:

2020052701


IKS の準備作業はこれで終わりです。


【アプリケーションおよび GitHub の準備】
次に IKS にデプロイするアプリケーションと GitHub 側の準備を行います。まずはデプロイするアプリケーションを準備して Docker 対応(Dockerfile の準備)します。ここは実際に IKS にデプロイして使ってみたいアプリがある場合は個別に用意していただいても構いません。

一応サンプルとしてシンプルな Web アプリケーションを用意しました。自分でアプリケーションを用意しない場合はこちらをお使いください:
https://github.com/dotnsf/hostname_githubactions_iks


このサンプルを使う場合は、まず GitHub にログインした上で上記 URL を開きます。そして画面右上の "fork" を選択し、自分自身のリポジトリとして複製してください:
2020052702


成功すると https://github.com/XXXXX/hostname_githubactions_iks というリポジトリができあがります(XXXXX 部分は各ユーザー個別の ID 名)。以降はこのリポジトリを使って GitHub Actions を利用します。まずこの URL を指定してリポジトリの内容をローカルに clone しておきます(XXXXX 部分は自分の ID に置き換えて実行してください):
$ git clone https://github.com/XXXXX/hostname_githubactions_iks

これでサンプルアプリケーションのソースコードがローカルファイルとしてクローンされ、ローカルファイルとして参照することができるようになりました。簡単にアプリケーションの内容を紹介しておくと、このサンプルアプリケーションは Node.js で実装されており、 GET / という HTTP リクエストに対して /etc/hostname ファイルの内容を text/plain でそのまま返すだけの機能を持っています。本体である app.js ファイルの全容は以下(これで全部)で、実質20行足らずの実装です。またポート番号は 8080 番で固定しています:
//.  app.js
var express = require( 'express' ),
    fs = require( 'fs' ),
    app = express();

app.get( '/', function( req, res ){
  res.contentType( 'text/plain; charset=utf-8' );
  fs.readFile( '/etc/hostname', "utf-8", function( err, text ){
    if( err ){
      res.write( JSON.stringify( err, 2, null ) );
      res.end();
    }else{
      res.write( text );
      res.end();
    }
  });
});

var port = 8080;
app.listen( port );
console.log( "server starting on " + port + " ..." );


この app.js と、依存ライブラリや起動コマンドが記述された package.json や、コンテナ対応のための Dockerfile などが含まれたアプリケーションとなっています。

繰り返しますが、実際に IKS にデプロイするアプリケーションは Docker 対応できていれば他のものを使っても構いません。ただしその場合はこのサンプルの .github/ フォルダ以下をまるごとそのアプリケーションプロジェクトにコピーしておいてください(実質的に .github/workflows/iks.yml ファイル1つだけのフォルダで、この iks.yml が Github Actions を IKS で使うためのワークフローを定義しています)。

iks.yml ファイルの中身はこの後で参照しますが、このファイルは Github のプロジェクトのシークレット情報を参照して動くよう定義されています。したがって必要なシークレット情報を Github プロジェクト内にあらかじめ定義しておく必要があります。

そのための設定を行います。 https://github.com/XXXXX/hostname_githubactions_iks を開き、"Settings" タブを選んで左メニューから "Secrets" を選択します。このプロジェクトに設定されたシークレット情報の一覧が表示されますが、最初は空のはずです。ここにシークレット情報を追加するため "New secret" ボタンをクリックします:
2020052703


シークレット情報は名前(Name)と値(Value)の組で定義します。まずは "IBM_CLOUD_API_KEY" という名前のシークレットを定義します。この値としては上述した IBM Cloud 利用時に作成してダウンロードするかクリップボードに保存した IBM Cloud API Key の値を入力します。入力後に "Add secret" ボタンをクリックして保存します:
2020052704


同様にしてもう1つ、"ICR_NAMESPACE" という名前で、 IBM Cloud CR の名前空間として作成した名称(上記例では dotnsf-ns ですが、個別に作成した時の値)を入力し、最後に "Add secret" をクリックします:
2020052705


つまりプロジェクトのシークレット情報として IBM_CLOUD_API_KEY と ICR_NAMESPACE の2つが定義されている状態にします。これで GitHub プロジェクト側の準備は完了です:
2020052706


最後に Github Actions のワークフロー定義を各自の内容に合わせて書き換えます。プロジェクト内の .github/workflows/iks.yml ファイルをテキストエディタで開きます。なおこのファイルの内容は以下のプロジェクトに含まれて提供されていたものをベースにしています:

https://github.com/IBM/actions-ibmcloud-iks


iks.yml を開き、20 行目以下(env: で始まる行以下)の値を編集します。といっても自分の独自アプリケーションではなく https://github.com/dotnsf/hostname_githubactions_iks からフォークしたプロジェクトのアプリケーションを使う場合はほぼそのままでも動くはずです:
2020052707


編集の必要な箇所があるとすれば以下です:
変数名設定内容既定値
IBM_CLOUD_REGIONIBM Cloud で利用する IKS のリージョン(地域)us-south
REGISTRY_HOSTNAME内部で利用する Docker Hub のホスト名us.icr.io
IMAGE_NAMEDocker イメージを保存する時の名称hostname
IKS_CLUSTERIKS 作成時に指定したクラスタ名mycluster
DEPLOYMENT_NAMEDocker イメージを IKS にデプロイする時の DEPLOYMENT の名称hostname
PORTアプリケーションが HTTP アクセス時に LISTEN するポートの番号
8080


ここまでの作業で hostname_githubactions_iks プロジェクト(Github にコミットするプロジェクト)のファイルに変更を加えている場合はこれで準備完了です。加えていない場合はコミットに変化を加えるというだけの目的でいずれかのファイルに挙動に支障のない変更を加えておいてください。例えば README.md ファイルの最後に空の1行を追加する、などでも構いません(苦笑):
2020052708


これで全ての事前準備が整いました。


【GitHub Actions の実行】
ではこのプロジェクトを Github にコミット&プッシュします:
$ cd hostname_githubactions_iks

$ git add .

$ git commit -m 'README.md updated.'

$ git push


Push の成功と同時に Github Actions が実行されます。リポジトリのページ(https://github.com/XXXXX/hostname_githubactions_iks )を開き、"Actions" タブを選択すると、コミットコメントの横にクルクル回るアイコンが表示され、Github Actions に定義された内容(iks.yml の内容)に従ってアプリケーションイメージが内部 Docker Hub に登録され、そこから IKS へデプロイされていきます:
2020052709


しばらくすると一連の作業が完了します。下図のように緑のチェックマークが表示されていればコマンドが成功しています(赤いバツは失敗):
2020052710


この箇所をクリックすることでワークフローで実行された内容の詳細を確認したり、(失敗している場合は)どこで失敗しているかを確認することもできます:
2020052700


この時点でアプリケーションは IKS 内にデプロイされ動いており、パブリックに公開されています。では実際に動いているアプリケーションにアクセスしてみたいのですが、そのためにはアクセスするための情報(IP アドレスおよびポート番号)を見つける必要があります。

まず公開されているパブリック IP アドレスは IBM Cloud ログイン後のダッシュボードから確認することができます。作成した IKS のワーカーノードを表示し、稼働しているサービスのパブリック IP を確認します。これが外部アクセス用の公開 IP アドレスとなっています:
2020052701


またポート番号は Web 端末から "kubectl get svc" を実行し、サービス名(iks.yml で指定したデプロイ名、上述の例だと hostname)の PORT(S) 列を参照します。 "80:*****/TCP" と表示されている ***** 部分(下図の場合は 32284)がサービスにアクセスするためのポート番号となります:
2020052702


これらを組み合わせてウェブブラウザまたは curl コマンドなどで http://(IP アドレス):(ポート番号) にアクセスします。下図の例では http://173.193.112.74:32284/ へアクセスしています。またその実行結果として(コンテナの /etc/hostname の中身である hostname-59cb7b958f-lwv52 という値が表示されています。この結果は実行環境によって異なりますが、実際に稼働しているコンテナから取得した値となっています)。実際の稼働環境にアクセスして挙動を確認することもできました:
2020052703


この時点で IKS 上で稼働しているので、インスタンスのスケールイン/スケールアウトといった操作も可能です。

またこの状態から更にアプリケーションのソースコードを改良するなどした上で、再度 git commit & git push すると同じワークフロープロセスが動き、自動的に IKS 内に最新コードのアプリケーションがデプロイされる、という CI/CD 環境が構築できました。


【デプロイしたポッド等を削除する場合】
最後に環境を削除する手順を紹介します。IKS そのものごと削除する場合は(IBM Cloud のメニューから IKS ごと削除すればよいので)ある意味で簡単ですが、IKS を残して IKS 内にデプロイしたアプリケーション環境を削除する場合の手順を紹介します。

まず Web 端末で "kubectl get all" を実行して、ポッドやサービス、デプロイメントの情報を確認します:
2020052704


この実行結果から hostname 関連のポッド、サービス、デプロイメントを削除します。Web 端末で続けて以下のコマンドを実行します(service/kubernetes のサービスは IKS そのものなので削除しないよう気をつけてください):
$ kubectl delete deployment hostname

$ kubectl delete service hostname

$ kubectl delete pod hostname-59cb7b958f-lwv52

最後にもう一度 "kubectl get all" を実行して、hostname 関連のものが残っていないことを確認します:
2020052705


上記のような結果になれば無事に削除できました。
 

コンテナオーケストレーション環境である 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つ超えた、かも!


このページのトップヘ