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

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

タグ:proxy

まず最初に、自分がもともと今回勝利する技術に興味を持ったきっかけは 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

社内ネットワークに Proxy サーバーが設置されている環境は珍しくないと思いますが、そのような環境下でパブリッククラウドを利用して開発作業を行ったり、アプリケーションをデプロイする際に Proxy 環境に応じた設定が必要になります(ついでにいうと、そのような環境下では特定ポート以外を通さない設定になっていることも多いので、単なる Proxy 対応だけでは不十分なこともあります)。

実際にそのような環境でのお客様対応を通じて苦労した得た情報を設定手順含めて共有します。なお以下は IBM Cloud を使ったケースとして紹介していますが、そこそこ広くパブリッククラウド利用時に役立つ情報だと思っています。


【Proxy 環境下で git コマンドを使う】
コマンドプロンプトやターミナルを開いて以下を入力します:
$ git config --global http.proxy http://my.proxyserver.com:8080

$ git config --global https.proxy http://my.proxyserver.com:8080


なお http://my.proxyserver.com:8080 部分は Proxy サーバー名およびポート番号です(以下も同様)。


【ssh でなく https で github(gitlab) を使う】
これは Proxy とは直接関係ないのですが、ssh プロトコル通信が閉じられているような環境下でプライベートな github(gitlab) リポジトリを使いたい場合の、つまり https プロトコルでプライベートな github(gitlab) リポジトリを使う場合に必要な設定項目です。 作業内容としては Private Access Token を設定することで https でも認証が可能になり、プライベートリポジトリを利用することができるようになります。以下 github を使う前提での画面で紹介しますが、gitlab でもほぼ同様です。

まず github にログインし、画面右上の "Settings"  を選択します:
2020022801


次に画面左の "Developer Settings" を選択します:
2020022802


Developer Settings のメニューから "Personal access tokens" を選択し、画面右の "Generate new token" ボタンをクリックします:
2020022803


新たに生成するトークンの設定を指定します。まず名前を(myToken など)適当に入力し、scopes を選択します(わからなければとりあえずは全部):
2020022804


そして最後に画面下部にある "Generate token" ボタンをクリック:
2020022805


すると以下のような画面になり、トークン文字列が表示されます。この文字列はこの一回しか表示されません(一度異なるページを表示した後に再度表示する方法は用意されていません)。別ファイルにコピーするなどしてこの値を再度入力できるようにしてください:
2020022806

ここまで完了していれば、以下のコマンドで https プロトコルだけで github から git clone ができます:
$ git clone https://github.com/aabbcc/xxyyzz.git
 Username: (GitHub のユーザー名)
 Password: (取得したトークン文字列)

なお gitlab の場合は以下のようになります:
$ git clone https://gitlab.com/aabbcc/xxyyzz.git
 Username: oauth2
 Password: (取得したトークン文字列)

【Proxy 環境下で npm コマンドを使う】
サーバーサイド JavaScript である Node.js を使ってアプリケーションを開発する場合、ほぼ npm コマンドを併用することになると思っています。この npm コマンドを Proxy 環境下で使う場合にも設定が必要です。
$ npm -g config set proxy "http://my.proxyserver.com:8080/"
$ npm -g config set https-proxy "http://my.proxyserver.com:8080/"
$ npm -g config set registry "http://registry.npmjs.org/"

proxy と https-proxy の設定をすれば動くはず、ですが、この2つだけではエラーになることがあるらしいです。その場合は registry も設定してください。


【Proxy 環境下で cf コマンドを使う】
これは IBM Cloud 環境に特化した設定かもしれませんが、PaaS である Cloud Foundry ランタイムにアプリケーションを push(デプロイ)する際に利用する cf コマンド(ibmcloud cf コマンド)も Proxy 環境下ではそのための環境設定をしないと使うことはできません。

※Delivery Pipeline サービスを利用することで、cf コマンドを使わずに Git と連動してデプロイすることは可能です。


具体的には環境変数の設定を行う必要があります。以下は Windows 10 での環境変数設定方法です。

コントロールパネル - システムとセキュリティ - システム - システムの詳細設定 を選択します:
2020022807


「詳細設定」タブの「環境変数」ボタンをクリック:
2020022808


ユーザー環境変数で「新規」ボタンをクリック:
2020022809


新しいユーザー変数として、以下の2つを設定します:
変数名変数値
http_proxyProxy サーバー URL
https_proxyProxy サーバー URL

20200228010


2つの環境変数が新たに追加されていることを確認します:

20200228011


これで cf コマンドを指定した Proxy サーバー経由で実行することができるようになりました。

このページのトップヘ