taxin's notes

読書、勉強メモ etc.

Kubernetes the hard way (GCP版) #6 (データ暗号化の設定とキーの生成)

Kubernetes the hard way (GCP版)の続きです。

前回の記事はこちら。

taxintt.hatenablog.com

Chap6. Generating the Data Encryption Config and Key(データ暗号化の設定とキーの生成)

このチャプターでは、etcdの暗号化のために使用する設定ファイルと暗号化キーを利用します。
(作成したconfigファイルはChap8.でAPI Serverの設定時に利用されます。)

暗号化キーの作成

最初にetcdの暗号化のベースとなる暗号化キーを作成します。
ざっくり説明するとランダムな文字列 (32byte) を生成して、それをbase64エンコードしたものを暗号化キーとして利用します。

~/w/k/h/04 ❯❯❯ ENCRYPTION_KEY=$(head -c 32 /dev/urandom | base64)
~/w/k/h/04 ❯❯❯ echo $ENCRYPTION_KEY
AOSngvXL8pGASyTzG0AqvNzPL6IYk3Spg+Jlhd+pI0Q=


最初のheadコマンドで指定している/dev/urandomはランダムな文字列を出力する擬似デバイスファイルの一種です。
同じようにランダムな文字列を出力するデバイスファイルとしてdev/randomも存在します。

oplern.hatenablog.com

暗号化設定ファイルの作成

次に、暗号化設定ファイルであるEncryptionConfigを作成します。
resources.providersの部分では暗号化アルゴリズムの一つであるAES-CBCを指定しています。

KubernetesのDocumentの中でも、暗号強度の観点からAES-CBC (aescbc)の利用が推奨となっています。

The recommended choice for encryption at rest but may be slightly slower than secretbox

kubernetes.io

~/w/k/h/04 ❯❯❯ cat > encryption-config.yaml <<EOF
kind: EncryptionConfig
apiVersion: v1
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: ${ENCRYPTION_KEY}
      - identity: {}
EOF


EncryptionConfigを作成したら、各Control Planeに配布していきます。

~/w/k/h/04 ❯❯❯ for instance in controller-0 controller-1 controller-2; do
  gcloud compute scp encryption-config.yaml ${instance}:~/
done
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
encryption-config.yaml                                                                                                                                 100%  240     0.7KB/s   00:00
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
encryption-config.yaml                                                                                                                                 100%  240     2.4KB/s   00:00
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
encryption-config.yaml

(+α) etcdの暗号化キーについて

etcdの暗号化キーに関連する内容を2点補足しておきます。

暗号化方式 (共通鍵暗号化方式)について

暗号化と復号に同じ鍵を利用する方式のことを共通鍵暗号化方式を呼び、この方式の暗号化キーを用いてetcdを暗号化します。
(共通鍵暗号で使用する暗号化アルゴリズムの一つとしてAESがあります。)

ちなみにAPI Serverとの暗号化通信(TLS)では、公開鍵暗号方式によって共通鍵の交換を行った上で暗号化通信は共通鍵暗号化方式で行います

milestone-of-se.nesuke.com

暗号化アルゴリズム (AES-CBC with PKCS#7 padding)について

暗号化設定ファイルの中では、暗号化アルゴリズムとしてaescbc (AES-CBC with PKCS#7 padding)が指定されています。

最初に暗号化アルゴリズムとして、ブロック暗号の一種であるAESが指定されています。
平文をある単位のブロックに分割して暗号化する方式をブロック暗号と呼び、AESでは128bit (=16byte)単位のブロックに分割します。

また、ブロック暗号のモードの一つであるCBCも指定されています。
平文の各ブロックは前の暗号文とのXORを取ってから暗号化されます。(平文の最初のブロックに関しては「前の暗号文」が存在しないので、初期化ベクトルと呼ばれるものを利用します。)

ja.wikipedia.org

先ほども記載した通りAES暗号はブロック方式のため、16の倍数byteの平文を暗号化します。
そのため、16の倍数byteに満たない場合は16の倍数byteになるようにデータを追加 (padding) する必要があります。

そのpaddingの方式の一つとしてPKCS#7 paddingがあり、こちらもaescbcを指定することで自動的に指定されています。

qiita.com

設定時には、ブロック暗号のモードやpaddingの方式などユーザー側で変更することはできないので注意が必要です。

今回は、Chap6の内容をまとめました。

Kubernetes the hard way (GCP版) #5 (認証用Kubernetes設定ファイルの作成)

Kubernetes the hard way (GCP版)の続きです。

前回の記事はこちら。

taxintt.hatenablog.com

Chap5. Generating Kubernetes Configuration Files for Authentication(認証用Kubernetes設定ファイルの作成)

このチャプターではChap4で作成した証明書を利用して、接続設定ファイルとして利用するkubeconfigファイルを作成します。

クライアント認証用のkubeconfigファイルの作成

controller managerkubeletkube-proxyscheduler・adminユーザー用のkubeconfigファイルを作成します。

最初に外部公開用のIPアドレスKUBERNETES_PUBLIC_ADDRESSとして取得します。
API Serverの前段に配置するELBで利用するIPアドレスをkubeconfig内で指定することで、Control PlaneのHA構成にも対応することが可能です。

(完全に余談ですが、Kubernetesクラスタの構築ツールの一つであるkubeadmでもHA構成のクラスタを作成できるようです。)

kubernetes.io

~/w/k/h/04 ❯❯❯ KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \
      --region $(gcloud config get-value compute/region) \
      --format 'value(address)')
~/w/k/h/04 ❯❯❯ echo $KUBERNETES_PUBLIC_ADDRESS
    35.233.237.51


次に、kubelet用のkubeconfigファイルを作成します。
各ノードごとにファイル名は${instance}.kubeconfigとして、kubeconfigファイルにクラスタに関する設定を追加していきます。

~/w/k/h/04 ❯❯❯ for instance in worker-0 worker-1 worker-2; do
  kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://${KUBERNETES_PUBLIC_ADDRESS}:6443 \
    --kubeconfig=${instance}.kubeconfig

  kubectl config set-credentials system:node:${instance} \
    --client-certificate=${instance}.pem \
    --client-key=${instance}-key.pem \
    --embed-certs=true \
    --kubeconfig=${instance}.kubeconfig

  kubectl config set-context default \
    --cluster=kubernetes-the-hard-way \
    --user=system:node:${instance} \
    --kubeconfig=${instance}.kubeconfig

  kubectl config use-context default --kubeconfig=${instance}.kubeconfig
done
Cluster "kubernetes-the-hard-way" set.
User "system:node:worker-0" set.
Context "default" created.
Switched to context "default".
Cluster "kubernetes-the-hard-way" set.
User "system:node:worker-1" set.
Context "default" created.
Switched to context "default".
Cluster "kubernetes-the-hard-way" set.
User "system:node:worker-2" set.
Context "default" created.
Switched to context "default".


引数や利用するファイルなどは変わりますが、同じコマンドを実行するため実行される各コマンドについて説明しておきます。

最初にkubectl config set-clusterコマンドでkubeconfigファイルにクラスタの情報を追加します。(下記の情報を引数で渡します。)

  • --certificate-authority: 作成したCA証明書ファイル (ca.pem)
  • --server: https://${KUBERNETES_PUBLIC_ADDRESS}:6443 (外部公開用のIPアドレスを用いたURL) を指定

kubeletの通信相手はControl PlaneのAPI Serverとなるため、--serverの部分ではAPI Serverの前段に配置予定のELB用のIPアドレスを用いたURLを指定します。

  kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://${KUBERNETES_PUBLIC_ADDRESS}:6443 \
    --kubeconfig=${instance}.kubeconfig


次に作成したクライアント証明書と鍵ファイルを指定して、kubectl config set-credentialsコマンドでkubeconfigファイルにクライアント証明書の情報を追加します。

  • --client-certificate: 作成したkubelet用のクライアント証明書 ( e.g.worker-0.pem)
  • --client-key: 作成したkubelet用のクライアント証明書の秘密鍵 ( e.g.worker-0-key.pem)
  kubectl config set-credentials system:node:${instance} \
    --client-certificate=${instance}.pem \
    --client-key=${instance}-key.pem \
    --embed-certs=true \
    --kubeconfig=${instance}.kubeconfig


最後に、kubectl config set-context defaultコマンドでクラスタのアクセス先情報であるcontextを作成します。

  • --cluster: kubeconfig内で指定するクラスタ名(kubernetes-the-hard-way)
  • --user: クライアント証明書内で指定したCN(Common Name) ( e.g.node:worker-0)

--userで指定するユーザー名は基本的に作成した証明書で指定したCNと同じ名前となります。

kubernetes.io

  kubectl config set-context default \
    --cluster=kubernetes-the-hard-way \
    --user=system:node:${instance} \
    --kubeconfig=${instance}.kubeconfig


次に、kube-proxy用のkubeconfigファイルを作成します。
各ノードごとにファイル名はkube-proxy.kubeconfigとして、先ほどと同じように設定を追加していきます。

同じコマンドを実行していますが、下記の引数は理解しておく必要があると思います。

  • --server: kube-proxyの通信相手は同じノード内のAPI Serverであるため、https://${KUBERNETES_PUBLIC_ADDRESS}:6443を指定
  • --user: クライアント証明書内で指定したCN(Common Name) であるsystem:kube-proxyを指定
~/w/k/h/04 ❯❯❯ {
  kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://${KUBERNETES_PUBLIC_ADDRESS}:6443 \
    --kubeconfig=kube-proxy.kubeconfig

  kubectl config set-credentials system:kube-proxy \
    --client-certificate=kube-proxy.pem \
    --client-key=kube-proxy-key.pem \
    --embed-certs=true \
    --kubeconfig=kube-proxy.kubeconfig

  kubectl config set-context default \
    --cluster=kubernetes-the-hard-way \
    --user=system:kube-proxy \
    --kubeconfig=kube-proxy.kubeconfig

  kubectl config use-context default --kubeconfig=kube-proxy.kubeconfig
}
Cluster "kubernetes-the-hard-way" set.
User "system:kube-proxy" set.
Context "default" created.
Switched to context "default".


同様に、kube-scheduler・kube-controller-manager用のkubeconfigファイルを作成します。
Control Planeで利用するkubeconfigファイルを作成して、先ほどと同じように設定を追加していきます。

一部の引数について説明を記載しておきます。

  • --server: 2つのコンポーネントの通信相手は同じノード内のAPI Serverとなるため、ループバックアドレスを用いてhttps://127.0.0.1:6443を指定
  • --user: クライアント証明書内で指定したCN(Common Name) を指定 (system:kube-controller-manager, system:kube-scheduler)

qiita.com

~/w/k/h/04 ❯❯❯ {
  kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://127.0.0.1:6443 \
    --kubeconfig=kube-controller-manager.kubeconfig

  kubectl config set-credentials system:kube-controller-manager \
    --client-certificate=kube-controller-manager.pem \
    --client-key=kube-controller-manager-key.pem \
    --embed-certs=true \
    --kubeconfig=kube-controller-manager.kubeconfig

  kubectl config set-context default \
    --cluster=kubernetes-the-hard-way \
    --user=system:kube-controller-manager \
    --kubeconfig=kube-controller-manager.kubeconfig

  kubectl config use-context default --kubeconfig=kube-controller-manager.kubeconfig
}
Cluster "kubernetes-the-hard-way" set.
User "system:kube-controller-manager" set.
Context "default" created.
Switched to context "default".

~/w/k/h/04 ❯❯❯ {
  kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://127.0.0.1:6443 \
    --kubeconfig=kube-scheduler.kubeconfig

  kubectl config set-credentials system:kube-scheduler \
    --client-certificate=kube-scheduler.pem \
    --client-key=kube-scheduler-key.pem \
    --embed-certs=true \
    --kubeconfig=kube-scheduler.kubeconfig

  kubectl config set-context default \
    --cluster=kubernetes-the-hard-way \
    --user=system:kube-scheduler \
    --kubeconfig=kube-scheduler.kubeconfig

  kubectl config use-context default --kubeconfig=kube-scheduler.kubeconfig
}
Cluster "kubernetes-the-hard-way" set.
User "system:kube-scheduler" set.
Context "default" created.
Switched to context "default".


最後に、adminユーザー用のkubeconfigファイルを作成します。
このkubeconfigファイルはクラスタと通信するクライアント側で利用するkubeconfigファイルではないので注意が必要です。
(Chap8. でControl Plane内で各種設定を行う際に利用する用のconfigファイルだと考えられます。)

一部の引数について説明を記載しておきます。

  • --server: Control Plane内で利用するkubeconfigファイルなので、ループバックアドレスを用いてhttps://127.0.0.1:6443を指定
  • --user: クライアント証明書内で指定したCN(Common Name) を指定 (admin)
w/k/h/04 ❯❯❯ {
  kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://127.0.0.1:6443 \
    --kubeconfig=admin.kubeconfig

  kubectl config set-credentials admin \
    --client-certificate=admin.pem \
    --client-key=admin-key.pem \
    --embed-certs=true \
    --kubeconfig=admin.kubeconfig

  kubectl config set-context default \
    --cluster=kubernetes-the-hard-way \
    --user=admin \
    --kubeconfig=admin.kubeconfig

  kubectl config use-context default --kubeconfig=admin.kubeconfig
}
Cluster "kubernetes-the-hard-way" set.
User "admin" set.
Context "default" created.
Switched to context "default".


kubeconfigファイルの作成が完了したら、コンポーネントが存在するNodeにファイルを配布していきます。

w/k/h/04 ❯❯❯ for instance in worker-0 worker-1 worker-2; do
  gcloud compute scp ${instance}.kubeconfig kube-proxy.kubeconfig ${instance}:~/
done
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
worker-0.kubeconfig                                                                                                                                    100% 6383    59.0KB/s   00:00
kube-proxy.kubeconfig                                                                                                                                  100% 6321    65.0KB/s   00:00
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
worker-1.kubeconfig                                                                                                                                    100% 6383    31.4KB/s   00:00
kube-proxy.kubeconfig                                                                                                                                  100% 6321    61.9KB/s   00:00
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
worker-2.kubeconfig                                                                                                                                    100% 6383    61.1KB/s   00:00
kube-proxy.kubeconfig                                                                                                                                  100% 6321    60.4KB/s   00:00

~/w/k/h/04 ❯❯❯ for instance in controller-0 controller-1 controller-2; do
  gcloud compute scp admin.kubeconfig kube-controller-manager.kubeconfig kube-scheduler.kubeconfig ${instance}:~/
done
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
admin.kubeconfig                                                                                                                                       100% 6265    61.8KB/s   00:00
kube-controller-manager.kubeconfig                                                                                                                     100% 6387    57.8KB/s   00:00
kube-scheduler.kubeconfig                                                                                                                              100% 6337    59.1KB/s   00:00
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
admin.kubeconfig                                                                                                                                       100% 6265    22.8KB/s   00:00
kube-controller-manager.kubeconfig                                                                                                                     100% 6387    60.8KB/s   00:00
kube-scheduler.kubeconfig                                                                                                                              100% 6337    63.9KB/s   00:00
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
admin.kubeconfig                                                                                                                                       100% 6265    52.7KB/s   00:00
kube-controller-manager.kubeconfig                                                                                                                     100% 6387    58.5KB/s   00:00
kube-scheduler.kubeconfig                                                                                                                              100% 6337    64.2KB/s   00:00
~/w/k/h/04 ❯❯❯


今回は、Chap5の内容をざっくりまとめました。
他に参考にした資料のリンクはこちらです。

Kubernetesのユーザー管理と認証・権限確認機構を理解しよう | さくらのナレッジ

Kubernetes the hard way (GCP版) #4 (CAのプロビジョニングとTLS証明書の生成)

Kubernetes the hard way (GCP版)の続きです。

前回の記事はこちら。 taxintt.hatenablog.com

Chap4. Provisioning a CA and Generating TLS Certificates (CAのプロビジョニングとTLS証明書の生成)

このチャプターでは、Kubernetesクラスター内で利用するTLS証明書の作成を行います。
認証局(CA)の構成ファイル・CA証明書を作成し、それらを利用してTLS証明書を作成するという流れとなっています。

先に、ハンズオンの手順を一通り記載した後に解説を記載していきます。

CAのプロビジョニング

最初にTLS証明書の作成に必要な認証局(CA)の構成ファイルを作成します。
構成ファイルの中では、証明書の利用用途と有効期限をセットした証明書発行に利用するプロファイル(Kubernetes)が定義されています。

github.com

TLS証明書の作成時には、作成した構成ファイルとプロファイル(Kubernetes)を指定します。

// 認証局(CA)の構成ファイルを作成する
~/w/k/h/04 ❯❯❯ {

cat > ca-config.json <<EOF
{
  "signing": {
    "default": {
      "expiry": "8760h"
    },
    "profiles": {
      "kubernetes": {
        "usages": ["signing", "key encipherment", "server auth", "client auth"],
        "expiry": "8760h"
      }
    }
  }
}
EOF


次に、CA証明書とCAの秘密鍵の作成を行います。 証明書の作成に必要なCSR (証明書署名要求) を作成した上で、CSRを基に証明書と秘密鍵を作成します。

// CSRを作成する
cat > ca-csr.json <<EOF
{
  "CN": "Kubernetes", 
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "Kubernetes",
      "OU": "CA",
      "ST": "Oregon"
    }
  ]
}
EOF

// CSRを基にCA証明書とCAの秘密鍵を生成する
cfssl gencert -initca ca-csr.json | cfssljson -bare ca

}
2020/02/29 11:00:07 [INFO] generating a new CA key and certificate from CSR
2020/02/29 11:00:07 [INFO] generate received request
2020/02/29 11:00:07 [INFO] received CSR
2020/02/29 11:00:07 [INFO] generating key: rsa-2048
2020/02/29 11:00:07 [INFO] encoded CSR
2020/02/29 11:00:07 [INFO] signed certificate with serial number 254290516728701407670608002579038214846567871824

// CA証明書(ca.pem)とCAの秘密鍵(ca-key.pem)が生成されている
~/w/k/h/04 ❯❯❯ ls
ca-config.json ca-csr.json    ca-key.pem     ca.csr         ca.pem

TLS証明書の作成

認証局(CA)の構成ファイル、CA証明書、CAの秘密鍵を基に各種証明書を作成します。
作成する証明書は下記の通りです。

  • adminユーザー用クライアント証明書
  • Kubelet用クライアント証明書
  • Controller Manager用クライアント証明書
  • Kube Proxy用クライアント証明書
  • Scheduler用クライアント証明書
  • KubernetesAPI Server用証明書
  • サービスアカウントのキーペア

CSR (証明書署名要求) を作成し、それを基にTLS証明書と秘密鍵を作成する部分は先程の手順と同じです。
これ以降は、認証局(CA)の構成ファイル、CA証明書と秘密鍵を指定して証明書の作成を行います。

最初に、adminユーザー用のクライアント証明書を作成します。
特記事項は下記の通りです。

  • CSRの識別名(DN)の一つであるOrganizationでsystem:mastersを指定する
~/w/k/h/04 ❯❯❯ {

cat > admin-csr.json <<EOF
{
  "CN": "admin",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "system:masters",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

// クライアント証明書の作成時にCAの証明書・秘密鍵・構成ファイルを引数として渡している
cfssl gencert \
  -ca=ca.pem \ 
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -profile=kubernetes \
  admin-csr.json | cfssljson -bare admin

}
2020/02/29 11:13:46 [INFO] generate received request
2020/02/29 11:13:46 [INFO] received CSR
2020/02/29 11:13:46 [INFO] generating key: rsa-2048
2020/02/29 11:13:47 [INFO] encoded CSR
2020/02/29 11:13:47 [INFO] signed certificate with serial number 679971876887522464623843539440976113444619121269
2020/02/29 11:13:47 [WARNING] This certificate lacks a "hosts" field. This makes it unsuitable for
websites. For more information see the Baseline Requirements for the Issuance and Management
of Publicly-Trusted Certificates, v.1.1.6, from the CA/Browser Forum (https://cabforum.org);
specifically, section 10.2.3 ("Information Requirements").

//admin-key.pem, admin.pemが作成されている
~/w/k/h/04 ❯❯❯ ls
admin-csr.json admin-key.pem  admin.csr      admin.pem      ca-config.json ca-csr.json    ca-key.pem     ca.csr         ca.pem


次に、Kubelet用のクライアント証明書を作成します。 特記事項は下記の通りです。

  • 識別名(DN)のOrganizationとしてsystem:nodesを指定する
  • API Server側でNode Authorizerを利用したリクエストの許可設定を行うために、証明書のCommon Name(CN)としてsystem:node:${instance}を指定する
  • hostnameとしてノードのホスト名(${instance})とIP(${INTERNAL_IP})、(Chap3で作成した)外部公開用のIPアドレス(${EXTERNAL_IP})を渡している


qiita.com

Kubeletは(API Serverに対しての)クライアントでもありますが、サーバとしての役割も果たすため-hostnameで複数のホスト名を渡します。
これにより、証明書にはCNの別名であるSANs(Subject Alternative Names)が指定されます。

また、-profile=kubernetesKubernetesのプロファイルからusageを参照することで、作成した証明書がクライアント兼サーバ証明書として利用できるようになってると考えられます。

~/w/k/h/04 ❯❯❯ for instance in worker-0 worker-1 worker-2; do
cat > ${instance}-csr.json <<EOF
{
  "CN": "system:node:${instance}",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "system:nodes",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

EXTERNAL_IP=$(gcloud compute instances describe ${instance} \
  --format 'value(networkInterfaces[0].accessConfigs[0].natIP)')

INTERNAL_IP=$(gcloud compute instances describe ${instance} \
  --format 'value(networkInterfaces[0].networkIP)')

cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -hostname=${instance},${EXTERNAL_IP},${INTERNAL_IP} \
  -profile=kubernetes \
  ${instance}-csr.json | cfssljson -bare ${instance}
done
2020/02/29 11:16:58 [INFO] generate received request
2020/02/29 11:16:58 [INFO] received CSR
2020/02/29 11:16:58 [INFO] generating key: rsa-2048
2020/02/29 11:16:58 [INFO] encoded CSR
2020/02/29 11:16:58 [INFO] signed certificate with serial number 359886775191352144581584441997016046901027647498
2020/02/29 11:17:01 [INFO] generate received request
2020/02/29 11:17:01 [INFO] received CSR
2020/02/29 11:17:01 [INFO] generating key: rsa-2048
2020/02/29 11:17:01 [INFO] encoded CSR
2020/02/29 11:17:01 [INFO] signed certificate with serial number 496107270789966248075618951568412965762056311780
2020/02/29 11:17:04 [INFO] generate received request
2020/02/29 11:17:04 [INFO] received CSR
2020/02/29 11:17:04 [INFO] generating key: rsa-2048
2020/02/29 11:17:04 [INFO] encoded CSR
2020/02/29 11:17:04 [INFO] signed certificate with serial number 356879757777462442721200413749147882778190289592

// 各ノード用の証明書(worker-X.pem)と秘密鍵(worker-X-key.pem)が作成されている
~/w/k/h/04 ❯❯❯ ls
admin-csr.json    admin.pem         ca-key.pem        worker-0-csr.json worker-0.pem      worker-1.csr      worker-2-key.pem
admin-key.pem     ca-config.json    ca.csr            worker-0-key.pem  worker-1-csr.json worker-1.pem      worker-2.csr
admin.csr         ca-csr.json       ca.pem            worker-0.csr      worker-1-key.pem  worker-2-csr.json worker-2.pem


Controller Manager〜Scheduler用のクライアント証明書の作成部分は省略します。
(adminユーザー用のクライアント証明書の作成と同じことをしているため)

次に、KubernetesAPI Server用の証明書を作成します。
Kubelet用のクライアント証明書同様、-hostnameでSANsを指定してTLS証明書を作成しています。

特記事項は下記の通りです。

Kubernetesクラスタを作成するとデフォルトでServiceが作成されていますが、その正体はAPI Servier用のService(Cluster IP)です。
内部DNSでは、kubernetes.defaultのホスト名でAレコードが登録されるため、それらのホスト名も指定する必要があります。

kubernetes.io

また、クラスタ内で利用するホスト名(${KUBERNETES_HOSTNAMES})については、別で作成予定のKubernetesのネットワークに関する記事の中で調べた内容をまとめる予定です。

~/w/k/h/04 ❯❯❯ {

KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \
  --region $(gcloud config get-value compute/region) \
  --format 'value(address)')

KUBERNETES_HOSTNAMES=kubernetes,kubernetes.default,kubernetes.default.svc,kubernetes.default.svc.cluster,kubernetes.svc.cluster.local

cat > kubernetes-csr.json <<EOF
{
  "CN": "kubernetes",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "Kubernetes",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -hostname=10.32.0.1,10.240.0.10,10.240.0.11,10.240.0.12,${KUBERNETES_PUBLIC_ADDRESS},127.0.0.1,${KUBERNETES_HOSTNAMES} \
  -profile=kubernetes \
  kubernetes-csr.json | cfssljson -bare kubernetes

}
2020/02/29 11:25:41 [INFO] generate received request
2020/02/29 11:25:41 [INFO] received CSR
2020/02/29 11:25:41 [INFO] generating key: rsa-2048
2020/02/29 11:25:41 [INFO] encoded CSR
2020/02/29 11:25:41 [INFO] signed certificate with serial number 603669054680267544191086535903497288645234008053


最後に、サービスアカウントに必要なキーペアを作成します。
Controller Manager(正確にはToken Controller)では、サービスアカウントのAPIリクエストに必要なトークンの生成を行います。
その際に作成したここで作成するキーペアを使用して、サービスアカウント用のトークンを生成して署名します。

qiita.com

~/w/k/h/04 ❯❯❯ {

cat > service-account-csr.json <<EOF
{
  "CN": "service-accounts",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "Kubernetes",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -profile=kubernetes \
  service-account-csr.json | cfssljson -bare service-account

}
2020/02/29 11:26:20 [INFO] generate received request
2020/02/29 11:26:20 [INFO] received CSR
2020/02/29 11:26:20 [INFO] generating key: rsa-2048
2020/02/29 11:26:20 [INFO] encoded CSR
2020/02/29 11:26:20 [INFO] signed certificate with serial number 372200916003664381145141033359158655751518600952
2020/02/29 11:26:20 [WARNING] This certificate lacks a "hosts" field. This makes it unsuitable for
websites. For more information see the Baseline Requirements for the Issuance and Management
of Publicly-Trusted Certificates, v.1.1.6, from the CA/Browser Forum (https://cabforum.org);
specifically, section 10.2.3 ("Information Requirements").


作成が完了した証明書は各ノードに配布します。
(CA証明書を各ノードに配布している理由については後述します。)

~/w/k/h/04 ❯❯❯ for instance in worker-0 worker-1 worker-2; do
  gcloud compute scp ca.pem ${instance}-key.pem ${instance}.pem ${instance}:~/
done
Warning: Permanently added 'compute.7526976075834096736' (ECDSA) to the list of known hosts.
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
ca.pem                                                                                                                                                 100% 1318    13.7KB/s   00:00
worker-0-key.pem                                                                                                                                       100% 1675    16.2KB/s   00:00
worker-0.pem                                                                                                                                           100% 1493    14.5KB/s   00:00
Warning: Permanently added 'compute.6311120260820139134' (ECDSA) to the list of known hosts.
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
ca.pem                                                                                                                                                 100% 1318    12.1KB/s   00:00
worker-1-key.pem                                                                                                                                       100% 1675    15.8KB/s   00:00
worker-1.pem                                                                                                                                           100% 1493    11.5KB/s   00:00
Warning: Permanently added 'compute.3606445772456098939' (ECDSA) to the list of known hosts.
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
ca.pem                                                                                                                                                 100% 1318    12.2KB/s   00:00
worker-2-key.pem                                                                                                                                       100% 1675    16.4KB/s   00:00
worker-2.pem          

~/w/k/h/04 ❯❯❯ for instance in controller-0 controller-1 controller-2; do
  gcloud compute scp ca.pem ca-key.pem kubernetes-key.pem kubernetes.pem \
    service-account-key.pem service-account.pem ${instance}:~/
done
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
ca.pem                                                                                                                                                 100% 1318    12.9KB/s   00:00
ca-key.pem                                                                                                                                             100% 1675    16.6KB/s   00:00
kubernetes-key.pem                                                                                                                                     100% 1679    16.9KB/s   00:00
kubernetes.pem                                                                                                                                         100% 1663    16.5KB/s   00:00
service-account-key.pem                                                                                                                                100% 1679    15.7KB/s   00:00
service-account.pem                                                                                                                                    100% 1440    13.7KB/s   00:00
Warning: Permanently added 'compute.3758247453048208529' (ECDSA) to the list of known hosts.
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
ca.pem                                                                                                                                                 100% 1318    13.6KB/s   00:00
ca-key.pem                                                                                                                                             100% 1675    17.3KB/s   00:00
kubernetes-key.pem                                                                                                                                     100% 1679    17.5KB/s   00:00
kubernetes.pem                                                                                                                                         100% 1663    15.9KB/s   00:00
service-account-key.pem                                                                                                                                100% 1679    17.3KB/s   00:00
service-account.pem                                                                                                                                    100% 1440    14.6KB/s   00:00
Warning: Permanently added 'compute.3703518325472787566' (ECDSA) to the list of known hosts.
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':
ca.pem                                                                                                                                                 100% 1318    11.8KB/s   00:00
ca-key.pem                                                                                                                                             100% 1675    16.3KB/s   00:00
kubernetes-key.pem                                                                                                                                     100% 1679    16.7KB/s   00:00
kubernetes.pem                                                                                                                                         100% 1663    15.1KB/s   00:00
service-account-key.pem                                                                                                                                100% 1679    16.6KB/s   00:00
service-account.pem       


Control Planeに関連する証明書 f:id:taxintt:20200315100124p:plain

Worker Nodeに関連する証明書
(図の横幅の都合でWorker-2の記載は省いています) f:id:taxintt:20200315100128p:plain

ここまでがChap4のハンズオンの説明となります。

(+α) 証明書の役割とTLSの概要

そもそも、KubernetesにおいてTLS証明書がどのような役割を果たすかを整理しておきます。
結論から記載すると、KubernetesにおいてTLS証明書は通信の暗号化と各コンポーネントの認証のために利用されています。

kubernetes.io

Kubernetesの公式ドキュメント内のHow certificates are used by your clusterに証明書を利用しているコンポーネントと目的が記載されています。

  • Client certificates for the kubelet to authenticate to the API server
  • Server certificate for the API server endpoint
  • Client certificates for administrators of the cluster to authenticate to the API server
  • Client certificates for the API server to talk to the kubelets
  • Client certificate for the API server to talk to etcd
  • Client certificate/kubeconfig for the controller manager to talk to the API server
  • Client certificate/kubeconfig for the scheduler to talk to the API server

下の方にこのような記載もありましたが、Hard wayの内容に関係ない部分なので割愛します。 (front-proxyとetcdのクラスタ内で利用される証明書に関する説明だと思われます。)

  • Client and server certificates for the front-proxy
  • etcd also implements mutual TLS to authenticate clients and peers


上記から、API server (もしくはAPI Serverと通信するコンポーネント) がTLS証明書を利用していることがわかります。
また、Client certificateServer certificateの2種類のTLS証明書が発行されていることがわかると思います。


TLSの仕様と照らし合わせながら、KubernetesでのTLS証明書の利用用途を説明します。
この記事では、コンポーネントの認証に関連するTLSにおける認証に関する話をメインでまとめます。

TLSの仕様では通信相手 (=サーバー側) の認証が必須となっています。
具体的には、サーバー証明書の正当性検証とサーバーからの署名情報の検証によってサーバの認証(真正性の確認)を行います。
上記の目的のために、サーバー側であるAPI ServerはServer certificateを利用します。

また、TLSではクライアント証明書を利用したクライアント側の認証が可能となっています。
milestone-of-se.nesuke.com

こちらの記事にも、TLSクライアント認証に関する説明が記載されています。 jovi0608.hatenablog.com

このオプションを利用してAPI Serverに対して通信するクライアントの認証を行うため、クライアント側 (=通信元) である各コンポーネントClient certificateを利用します。

TLSの一連のシーケンスについて図示すると下記のようになります。

f:id:taxintt:20200315110117p:plain

証明書の正当性検証と署名情報の検証部分を詳細に図示するとこのようになります。

f:id:taxintt:20200315135645p:plain

サーバー証明書の正当性検証のために、CA証明書(正確には証明書にバンドルされている公開鍵)を利用していることがわかります。
上記のフローを実現するために、手順の中でWorker Nodeに認証局(CA)の証明書 (=ca.pem)を配布する必要があります。

また、署名情報の検証の部分では鍵交換で使うデータを秘密鍵で署名してクライアントに送付し、クライアントはサーバー証明書にバンドルされた公開鍵を用いて署名の検証を行っています。

> for instance in worker-0 worker-1 worker-2; do
     gcloud compute scp ca.pem ${instance}-key.pem ${instance}.pem ${instance}:~/
done

Warning: Permanently added 'compute.7526976075834096736' (ECDSA) to the list of known hosts.
Enter passphrase for key '/Users/.../.ssh/google_compute_engine':

// Worker Node(クライアント側となるNode)に認証局(CA)の証明書 (=ca.pem)を配布している
ca.pem                      
worker-0-key.pem           
worker-0.pem


また、クライアント証明書の検証について図示するとこのようになります。
Client Certificate以前のハンドシェイクのデータを秘密鍵で署名してサーバ側に送付し、サーバ側はクライアント証明書にバンドルされた公開鍵を用いて署名の検証を行っています。

f:id:taxintt:20200315135924p:plain


上記のようなフローにより、クライアント・サーバ側の両者の認証が行われます。
余談ですが、クライアント認証も行うTLSのことをmTLS (Mutual TLS authentication)と言います。(Istioに詳しい方は頻繁に聞く単語かもしれません。)

今回は、Chap4の内容をまとめました。
TLS周りの話は正確に理解する必要があるので、図や参考にした資料のリンクを全て載せておきました。

他に参考にした資料のリンクはこちらです。

TLS, HTTP/2演習

今なぜHTTPS化なのか?インターネットの信頼性のために、技術者が知っておきたいTLSの歴史と技術背景 - エンジニアHub|若手Webエンジニアのキャリアを考える!

パンドラの箱?TLS鍵交換の落とし穴、KCI攻撃とは何か - ぼちぼち日記

Kubernetes the hard way (GCP版) #3 (コンピューティングリソースのプロビジョニング)

Kubernetes the hard way (GCP版)の続きです。

前回の記事はこちら。 taxintt.hatenablog.com

Chap3. Provisioning Compute Resources(コンピューティングリソースのプロビジョニング)

このチャプターでは、コンピューティングリソース(=クラスタで利用するノード)のプロビジョニングを行います。

ネットワークの作成

最初に、今回作成するクラスタを作成するVPCを作成します
サブネットモードとしてcustomを指定することで、サブネットとサブネットのCIDRブロック(=IPの範囲)をユーザーが任意で設定することができます。

(参考)サブネットモードについて

cloud.google.com

> gcloud compute networks create kubernetes-the-hard-way --subnet-mode custom                                                                                     

...

Created [https://www.googleapis.com/compute/v1/projects/k8s-hardway-20200229/global/networks/kubernetes-the-hard-way].
NAME                     SUBNET_MODE  BGP_ROUTING_MODE  IPV4_RANGE  GATEWAY_IPV4
kubernetes-the-hard-way  CUSTOM       REGIONAL

Instances on this network will not be reachable until firewall rules
are created. As an example, you can allow all internal traffic between
instances as well as SSH, RDP, and ICMP by running:

$ gcloud compute firewall-rules create <FIREWALL_NAME> --network kubernetes-the-hard-way --allow tcp,udp,icmp --source-ranges <IP_RANGE>
$ gcloud compute firewall-rules create <FIREWALL_NAME> --network kubernetes-the-hard-way --allow tcp:22,tcp:3389,icmp


次に、VPCの中でサブネットを作成して、サブネットのCIDRブロックとして10.240.0.0/24を設定します。
/24なので256個分のIPアドレス枠がありますが、予約済みのアドレスとして4つ確保されているため残り252個分のIPアドレスを利用することが可能です。

(参考)予約済みIPアドレスについて

cloud.google.com

> gcloud compute networks subnets create kubernetes \
  --network kubernetes-the-hard-way \
  --range 10.240.0.0/24
Created [https://www.googleapis.com/compute/v1/projects/k8s-hardway-20200229/regions/us-west1/subnetworks/kubernetes].
NAME        REGION    NETWORK                  RANGE
kubernetes  us-west1  kubernetes-the-hard-way  10.240.0.0/24

続いて、作成したVPCに対してFirewall Ruleの設定を行います
--networkを用いてルールを設定するVPCを指定し、--allow--source-rangesを用いて許可する通信プロトコルと通信元のIPアドレスを設定します。

> gcloud compute firewall-rules create kubernetes-the-hard-way-allow-internal \
  --allow tcp,udp,icmp \
  --network kubernetes-the-hard-way \
  --source-ranges 10.240.0.0/24,10.200.0.0/16
Creating firewall...⠧Created [https://www.googleapis.com/compute/v1/projects/k8s-hardway-20200229/global/firewalls/kubernetes-the-hard-way-allow-internal].
Creating firewall...done.
NAME                                    NETWORK                  DIRECTION  PRIORITY  ALLOW         DENY  DISABLED
kubernetes-the-hard-way-allow-internal  kubernetes-the-hard-way  INGRESS    1000      tcp,udp,icmp        False

> gcloud compute firewall-rules create kubernetes-the-hard-way-allow-external \
  --allow tcp:22,tcp:6443,icmp \
  --network kubernetes-the-hard-way \
  --source-ranges 0.0.0.0/0
Creating firewall...⠶Created [https://www.googleapis.com/compute/v1/projects/k8s-hardway-20200229/global/firewalls/kubernetes-the-hard-way-allow-external].
Creating firewall...done.
NAME                                    NETWORK                  DIRECTION  PRIORITY  ALLOW                 DENY  DISABLED
kubernetes-the-hard-way-allow-external  kubernetes-the-hard-way  INGRESS    1000      tcp:22,tcp:6443,icmp        False

(+α) GCPFirewall ruleに対する補足

AWSでは、GCPFirewall ruleと同じようなものとしてSecurity Group(インスタンスレベル)やNetwork ACL(サブネットレベル)があります。
GCPFirewall ruleは下記の特徴を有しています。

  • ステートフルなFirewall(=戻りの通信は自動的に許可されるFirewall)
  • Allow/Denyの両方のルールを設定できる
  • ルールに対して優先度を設定できる

このハンズオンではVPC全体を指定する形でFirewall Ruleを定義しています。
要件によっては、インスタンスレベルで通信をフィルタリングしたい場面もあるかと思います。
GCPFirewall ruleでは、--target-tagsを用いて特定のインスタンス(タグ)を指定することでSecurity Groupのようなインスタンス単位でルールを設定できるようです。

(参考)--target-tagsを用いたFirewall Rule cloud.google.com

また、デフォルトのFirewall Ruleに関しては、リンク先に記載されている4つのルールが設定されています。
ルールとしてコンソールなどで表示されてはいませんが、Outbound通信はDefault Allow、Inbound通信はDefault Denyであることも把握しておく必要があります。

(参考)デフォルトのFirewall Ruleについて cloud.google.com

IPアドレスの払い出し

次に、API Serverの外部公開用のIPアドレスを払い出します。 このIPアドレスは、API Serverの前段に配置されるロードバランサーの固定IPとして利用されます。

~/Downloads ❯❯❯ gcloud compute addresses create kubernetes-the-hard-way \
  --region $(gcloud config get-value compute/region)
Created [https://www.googleapis.com/compute/v1/projects/k8s-hardway-20200229/regions/us-west1/addresses/kubernetes-the-hard-way].

~/Downloads ❯❯❯ gcloud compute addresses list --filter="name=('kubernetes-the-hard-way')"
NAME                     ADDRESS/RANGE  TYPE      PURPOSE  NETWORK  REGION    SUBNET  STATUS
kubernetes-the-hard-way  35.233.237.51  EXTERNAL                    us-west1          RESERVED

インスタンス(ノード)の作成

クラスタを構成するControl PlaneとWorkerをそれぞれ作成します。
今回は、HA構成のクラスタを作成するため、下記のような構成で作成します。

  • Control Plane:3
  • Worker Node:3
for i in 0 1 2; do
gcloud compute instances create controller-${i} \
    --async \
    --boot-disk-size 200GB \
    --can-ip-forward \
    --image-family ubuntu-1804-lts \
    --image-project ubuntu-os-cloud \
    --machine-type n1-standard-1 \
    --private-network-ip 10.240.0.1${i} \
    --scopes compute-rw,storage-ro,service-management,service-control,logging-write,monitoring \
    --subnet kubernetes \
    --tags kubernetes-the-hard-way,controller
done

Worker Nodeの作成時には--metadataで、各Nodeでデプロイ可能なpodのCIDRブロック(IPアドレスの範囲)を設定します。
設定したメタデータはCNIを用いたネットワーク周りの設定時に利用されます。

for i in 0 1 2; do
  gcloud compute instances create worker-${i} \
    --async \
    --boot-disk-size 200GB \
    --can-ip-forward \
    --image-family ubuntu-1804-lts \
    --image-project ubuntu-os-cloud \
    --machine-type n1-standard-1 \
    --metadata pod-cidr=10.200.${i}.0/24 \
    --private-network-ip 10.240.0.2${i} \
    --scopes compute-rw,storage-ro,service-management,service-control,logging-write,monitoring \
    --subnet kubernetes \
    --tags kubernetes-the-hard-way,worker
done

SSHアクセスの設定

インスタンスの作成が完了したら、作成したインスタンスSSHでのアクセスを試みます。
内部的にはGCP用のSSH keyの有無がチェックされて、保持していなければSSH Key-pairの生成を行いGCPに公開鍵を送信するということが行われています。

> gcloud compute ssh controller-0
WARNING: The public SSH key file for gcloud does not exist.
WARNING: The private SSH key file for gcloud does not exist.
WARNING: You do not have an SSH key for gcloud.
WARNING: SSH keygen will be executed to generate a key.
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /Users/...(省略).../.ssh/google_compute_engine.
Your public key has been saved in /Users/...(省略).../.ssh/google_compute_engine.pub.
The key fingerprint is:

...(省略)...

Updating project ssh metadata...⠼
Updated [https://www.googleapis.com/compute/v1/projects/<project_name>].
Updating project ssh metadata...done.
Waiting for SSH key to propagate.

Updating project ssh metadataというメッセージから想像がつくかと思いますが、生成したsshの公開鍵はプロジェクトレベルで共有されます。

そのため、1台目のインスタンスSSHでのアクセスが成功すると、同じパスワードを用いて他のインスタンスにもSSH接続が可能となります。

cloud.google.com

(+α) SSH keyの共有について

今回のハンズオンでは、全てのインスタンスで公開鍵を共有していますが、インスタンス個別の公開鍵を利用することも可能です。
その場合は、プロジェクト全体の公開 SSH 認証鍵をブロックした上でインスタンス レベルの公開 SSH 認証鍵を追加する必要があります。

(参考)プロジェクト全体の公開 SSH 認証鍵の使用について cloud.google.com

今回は、Chap3の内容をまとめました。
ハンズオンの手順的には難しい箇所は特にありませんが、Firewall RuleやSSHの鍵の扱いなどNWやセキュリティの観点で重要な内容なので正確に理解することを心がけたいと思います。

Kubernetes the hard way (GCP版) #1~2 (事前準備~ツールのインストール)

Kubernetesの学習の中で手をつけれていなかったKubernetes the hard way(GCP版)を最近完走しました。(AWSなど別のクラウド版のHard Wayも試す予定なのでGCP版と記載しています)

約1日かけてハンズオンを実施した後に内容を復習して、ある程度目処がついたのでブログ記事にしてまとめていきます。

Kubernetes The Hard wayを始めたきっかけ

Kubernetes Meetup Noviceの運営メンバーで「Kubernetes the hard wayは一度はやっておかないと...」という話になったのがきっかけです。
個々人でハンズオンを実施するのも面白くないので、予定を合わせて一緒にハンズオンを実施することになりました。(もくもく会的な感じでやりました)

余談ですが、今回はこのカフェでハンズオンをしていました。
(ご飯も美味しいのでおすすめです。自宅から近かったら間違いなくヘビロテしてる...)

www.google.com

きっかけはこんな感じですが、個人的には他にも下記のようなモチベーションがありました。

Kubernetes The Hard wayとは?

KubernetesクラスタGCP上に構築するというハンズオンです。
単にGKEクラスタを作成するのではなく、GCEのインスタンスクラスタに必要なコンポーネントを導入・設定してKubernetesクラスタを組むというものです。

Kubernetesクラスタに必要なコンポーネント(e.g. api-server, etcd...)の導入から行うので、クラスタ内部の仕様の理解にはもってこいのハンズオンです。

github.com

日本語版のレポジトリもあります。
英訳等の負担を少なくしてハンズオンを実施したい場合はこちらもおすすめです。

github.com

環境

下記のような環境でハンズオンを進めました。

  • MacOS:Mojave(v10.14.6)
  • Kubectl:v1.17.2
  • zsh / iTerm2 (結局tmuxは使いませんでした)

後述しますが、kubectlのバージョンによってはハンズオンの中でコマンドの実行結果が正常に取得できない部分があるので注意が必要です。
(v1.17.2では、クライアントPCからkubectl get component statusを実行しても出力結果が正常に出力できません。) github.com

前置きが長くなりましたが、ここからハンズオンの内容に入っていきます。

Chap1. prerequisites(前提条件)

GCPでは初学者向けに$300分の無料枠が用意されているので、そちらを利用します。
作成したリソース類を長期間放置しなければ、ハンズオンを実施しても無料枠の範囲に余裕で収まるはずです。
(自分の場合は 02/29(土)〜 03/02(月)までの3日間で約2300円程度のコストがかかっていました。)

Google Cloud SDKのインストール

MacOS版のドキュメントに従って、SDKのインストールを行います。

cloud.google.com

今回SDKを初めてインストールしたので、gcloud initコマンドで初期設定を行い自身のGoogleアカウントでログインしておきます。
次にハンズオンの記載通りに、デフォルトのリージョン(us-west1)とゾーン(us-west1-c)を指定して、プロジェクトの作成状況とコマンドの設定を確認します。

> gcloud projects list
PROJECT_ID             NAME                  PROJECT_NUMBER
k8s-hardway-20200229   k8s-hardway-20200229  xxxxx
...

> gcloud config list
[compute]
region = us-west1
zone = us-west1-c
[core]
account = ...
disable_usage_reporting = False
project = k8s-hardway-20200229

Chap2. installing the Client Tools(ツールのインストール)

ハンズオンではcfssl(cfssljson)kubectlを利用するため、それらもインストールしておきます。

cfssl(cfssljson)のインストール

cfsslの説明箇所に... be used to provision a PKI Infrastructureと書いてあり、意味を汲み取るのに少し苦労しました。

cfsslはTLS証明書やrootCA証明書などPKIに必要なものを一括で用意できる(=PKI Infrastructureをプロビジョニングできる)ツールです。
Chap4でcfsslを利用してTLS証明書の作成を行うので、その部分の記事でPKI関連の話もまとめようと思います。

opensslでも対応できそうですが、cfsslの方はjson形式でCSRを作成できるので、より簡潔に証明書の作成ができると感じました。

qiita.com

自分はパッケージマネージャーとしてHomebrewを利用しているため、brewコマンドでインストールを行いました。

brew install cfssl

kubectlのインストール

Kubectlに関してはインストール済みなので、手順としてはスキップしています。
(インストールする場合は、こちらもHomebrewでインストールを行うと思います。)

brew install kubectl

kubernetes.io

Chap1〜Chap2の内容を簡単にまとめました。
なるべく間が空かないように、次のチャプターの記事も書いていこうと思います。

【勉強メモ】Network Programming with Python #1

この記事では、下記の内容についてまとめます。

  • ソケット通信自体の説明
  • ソケット通信のフロー
  • ソケット通信を行うサンプルプログラム(サーバー側)

ソケットとは?

TCP/IPを利用して通信を行う際に必要な送受信処理をプログラム側で利用するためのインターフェース的なもの。
ソケットを用いた通信の裏側ではTCP/IP (or UDP) がベースとなっているので、勉強の第一歩として最適かと思いました。

下記のサイトでは、ソケット通信に対して下記のような記載をしています。

capm-network.com

ソケットとは、アプリケーションがデータを送受信するための仕組みを抽象化したものです。 ソフトウェア開発者は、ソケットの仕組みに則ってプログラムを記述すれば、具体的な通信手段や手順の詳細を知らなくても、通信を行うことができます。

他のサイトなども見た上で、ソケット通信に対するポイントは下記の通り。

  • ソケット通信の裏側にはTCP/IP (UDP) などのプロトコルが存在する
  • 開発者はTCP / IPの仕様を意識しないで、通信の処理を実装できる

Pythonでも、ソケット通信を行うためのライブラリーが提供されているのでこちらを利用する。

docs.python.org

ソケット通信のフロー

ソケット通信のフローは下記の通り。

【クライアント】

  1. ソケットの作成 (socket())
  2. ソケットに接続先のIPアドレスとポート番号を渡して接続要求を行う (connect())
  3. ソケットにデータを送信する (send())
  4. コネクションを終了する (close())

【サーバー】

  1. ソケットの作成 (socket())
  2. ソケットに接続を待ち受けるIPアドレスとポート番号を渡す (bind())
  3. 接続の受け付けを開始する (listen())
  4. 新しい接続要求を受け付ける (accept())
  5. ソケットからデータを受信する (recv())
  6. コネクションを終了する (close())

サンプルコード

今回はサーバー側のプログラムのみ準備しました。(コードはリンク先のサンプルを参考に少しだけ修正)
IPアドレス127.0.0.1、ポート番号:7777で接続を待ち受けるプログラムを用意して、クライアント側はncコマンドを利用して先ほどのIPアドレスとポートを利用して接続要求するようにしました。

qiita.com

import socket

def create_server_socket(host, port):
    # アドレスファミリ(AF_INET:Ipv4)とソケットタイプ(SOCK_STREAM:TCP)を指定
    server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # ソケットオプションでソケットの再利用フラグをONに設定
    # クライアントと通信途中で中断した場合同じIPアドレスとポートでバインドできるようにする
    server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    return server_sock


def accept_loop(server_sock):
    while True:
        # 4.新しい接続要求を受け付ける
        # server_sock.accept()はブロッキング処理なので、接続が来るまでは次の処理に進まない
        print("start accepting...")
        conn, (client_host, client_port) = server_sock.accept()

        # ソケットのファイル記述子、新規に作成されたソケットにBindされたIPアドレス・ポートを表示する
        print('[FD:{}] Accept: {}: {}'.format(conn.fileno(), client_host, client_port))

        # ソケットからデータを受信し、結果をbytesオブジェクトで返す(一度に512bytes受信する)
        data = conn.recv(512)
        print('[FD:{}] Recv: {}'.format(conn.fileno(), data))

        # 送信されてきたデータを返す
        conn.send(data)

        # 5.接続を切断する
        conn.close()

if __name__ == '__main__':
    host, port = "localhost", 7777

    # 1.ソケットの作成
    server_sock = create_server_socket(host, port)

    # 2.接続を待ち受けるIPアドレスとポートを指定する
    server_sock.bind((host, port))

    # 3.接続要求の受け付けを開始する
    server_sock.listen(5)
    print('Server Run Port: {}'.format(port))

    try:
        accept_loop(server_sock)
    except KeyboardInterrupt:
        server_sock.close()

検証

最初にサーバー側のプログラムを起動します。

❯ python simple_socket.py 
Server Run Port: 7777

netstatコマンドで確認するとポート:7777でLISTENしていることがわかります。 この段階でサーバー側では、ソケットの作成〜接続要求の受付開始まで完了している状態です。

❯ netstat -an | grep 7777
tcp4       0      0  127.0.0.1.7777         *.*                    LISTEN

次にncコマンドを実行して、先ほどのIPアドレス・ポートに接続要求を行います。

❯ nc localhost 7777

プログラムの標準出力・netstatコマンドの結果を見ると、接続要求を受け付けてTCPコネクションを確立した上でaccept()によって新たなソケットの作成(接続要求の受付まで対応可能)が行われていることがわかります。

❯ python simple_socket.py
Server Run Port: 7777
start accepting...
[FD:6] Accept: 127.0.0.1: 63897
❯ netstat -an | grep 7777
tcp4       0      0  127.0.0.1.7777         127.0.0.1.63897        ESTABLISHED
tcp4       0      0  127.0.0.1.63897        127.0.0.1.7777         ESTABLISHED
tcp4       0      0  127.0.0.1.7777         *.*                    LISTEN

最後にncコマンド側で任意のメッセージを送信します。
手順としては、コンソール上でncコマンドの実行後に任意の文字列を入力してエンターを押すだけです。

❯ nc localhost 7777
aaa
aaa

プログラム側の標準出力では、クライアント側から送信したメッセージaaaが受信できていることがわかります。

❯ python simple_socket.py
Server Run Port: 7777
start accepting...
[FD:6] Accept: 127.0.0.1: 63897
[FD:6] Recv: b'aaa\n'

netstatコマンドで確認すると、先ほどのコネクションが存在しないことが確認できます。 これはプログラム上でメッセージのやり取りをした後で、コネクションを解放する処理 (close()) を実行したためです。

❯ netstat -an | grep 7777
tcp4       0      0  127.0.0.1.7777         *.*                    LISTEN

3-way handshakeなど細かい部分はメソッドによって隠蔽されていますが、大まかな流れは掴めた気がします。 ncコマンドを利用すれば細かい状態遷移も再現できるみたいなので、別途検証してみようと思います。

わからない点

まだ、調べきれていない点を下記に列挙していきます。 (次回の記事で調べた内容もpostするかもしれません。)

  • close()の実行時には、どのようなフローを経てコネクションを解放しているのか? (アクティブクローズを行なっている?)
  • setsockopt()によってクライアントと通信途中で中断した場合に同じIPアドレスとポートでバインドできるが、これは「同じIPアドレスとポートを利用したソケットを作成できる」 or 「作成したソケットを再利用できる」のどちらが正しい?

qiita.com

【勉強メモ】Network Programming with Python #0

掲題の通り、自分が勉強した内容をまとめている記事です。
(#0ではモチベーションや参考リンクの整理をして、#1から学習内容をメモします)
誤字・脱字やまとめた内容に誤りがありましたらご指摘いただけると嬉しいです。

モチベーション

勉強内容

TCP/IP

  • ソケット通信のフロー
  • I/O多重化 (select, epoll)
  • マルチプロセス/スレッドによる多重化 (非同期I/O)
  • ノンブロッキングI/O

HTTP

  • 簡単なHTTPクライアントの実装 (Real World HTTP 3章を参照?)
  • Requestsモジュールを利用したHTTP通信

勉強したい内容は下記のサイトから抜き出してきました。
(勉強を進めるにつれて、全体像がわかってくるので勉強リストの内容は変わっていくはず...)

blog.fujimisakari.com

blog.amedama.jp

www.soudegesu.com

勉強メモ

(これから追記していきます)

1回目

taxintt.hatenablog.com