KubernetesクラスタをGCP上にスクラッチするチュートリアル「Kubernetes-The-Hard-Way」を和訳してくださった方がいらっしゃったので、取り組んでみました。

事前知識(調べたこと)

OCI(Open Cotainer Initiative)

  • コンテナのランタイムと、イメージ関連の、オープンな業界標準を作成する為に、2015/6にDocker社など複数の企業によって設立
  • 今は、Linux Foundation傘下のOSS団体

CRI(Container Runtime Interface)

  • kubeletとコンテナランタイムが通信する為のI/Fを規定したもの

containerd

containerdとは?

  • もともとDocker内部で利用され、Docker社が開発していたが、2017/3にCNCFに寄贈されたコンテナランタイム

runC

runCとは?

  • OCI仕様に基づいて、コンテナ生成、実行などを行うコンテナランタイム(コンテナを動かす為のプログラム)

Docker(containerd)との違い

  • Docker(containerd)は、コンテナの管理などをおこなう
  • runCは、コンテナ生成・実行をおこなう

gVisor

gVisorとは

  • Google社が開発してOSSとして公開している、準仮想化のような仕組みを用いて安全性を高めたコンテナランタイム
    • 従来のコンテナランタイムは、コンテナ間でOSカーネルを共有しているため、コンテナからOSのシステムコールを直接呼び出せる、セキュリティ問題を引き起こしやすい
    • これに対して、以下のとおり独自のgVisorレイヤを提供することで、「sandboxed containers」を実現している

これはなに

Introduction

  • 特になし

準備

概要

  • まずGCPの無料トライアルに登録した
  • gcloudがCLI経由でGCPのリソースを操作するツール
  • Regionはこんな感じ
  • Google Compute Engine(GCE)がいわゆるEC2

コマンド実行メモ

gloud init
# console上から`kubernetes-the-hard-way`プロジェクトを作成
gcloud config set project kubernetes-the-hard-way-XXXXXX
gcloud config set compute/region asia-northeast1

クライアントツールのインストール

概要

  • PKIの簡単な説明を読んだ
    • 通信する際に、お互いの身分証明ができれば、正しい相手と通信を行っていると信じることができる
    • 誰でも簡単に身分証明が作れたら、信用されない
      • 保険証や免許証と同じように、信頼できる期間が発行し、本人が大事に保管している身分証明書だけが有効になる
    • この身分証明書を証明書と呼び、証明書を発行する期間を認証局と呼び、認証局が発行した証明書を集中管理して利用者に配布する役割を持つリポジトリが呼ぶ
      • 証明書は単なるファイルである。X.509という規格で決まっている
    • 証明書の中には、情報を暗号化する為のも含まれている。

実行コマンド

brew install cfssl

利用するGCPリソースのプロビジョニング

概要

  • GCPにはProjectという概念がある。AWSで言う所のアカウントみたいなもの
  • Firewall(AWSでいう所のセキュリティグループ)がちょっと概念が違う
  • Workerノード作成時に、ノードが使用するPodネットワークのサブネットの値(pod-cidr)をGCEのメタデータに登録しておく
  • クラスタ全体のCIDRはあとで、Controller Managerの--cluster-cidrパラメータで指定することになる
  • gcloud compute ssh controller-0これでsshできる。簡単!

実行コマンド

  • VPCを作成
gcloud compute networks create kubernetes-the-hard-way --subnet-mode custom
  • サブネットを作成
gcloud compute networks subnets create kubernetes \
  --network kubernetes-the-hard-way \
  --range 10.240.0.0/24
  # [3] asia-northeast1
  • Firewallの設定
    • 全てのプロトコルの内部通信を許可する
    • 外部からのSSH,ICMP,HTTPSを許可する
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  
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
  • KubernetesのAPIサーバの前段の外部LBに付与するIPアドレスを払い出す
gcloud compute addresses create kubernetes-the-hard-way \
  --region $(gcloud config get-value compute/region)
  • Control Plane用のVMを作成
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用のVMを作成
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
  • 確認
gcloud compute instances list
  • sshで接続してみる
gcloud compute ssh controller-0

認証局(CA)の設定とTLS証明書の発行

概要

Kubernetesが使うTLS証明書

Client certificates for the kubelet to authenticate to the API server
  -> APIサーバに対して認証する為の、kubeletのクライアント証明書
Server certificate for the API server endpoint
  -> APIサーバのエンドポイントの、サーバ証明書
Client certificates for administrators of the cluster to authenticate to the API server
  -> APIサーバに対して認証する為の、クラスタの管理者のクライアント証明書
Client certificates for the API server to talk to the kubelets
  -> kubeletと通信する為の、APIサーバのクライアント証明書
Client certificate for the API server to talk to etcd
  -> etcdと通信する為の、APIサーバのクライアント証明書
Client certificate/kubeconfig for the controller manager to talk to the API server
  -> APIサーバと通信する為の、Controller Managerのクライアント証明書とkubeconfig
Client certificate/kubeconfig for the scheduler to talk to the API server.
  -> APIサーバと通信する為の、schedulerのクライアント証明書とkubeconfig
Client and server certificates for the front-proxy
  -> front-proxyのクライアント証明書及びサーバ証明書

Node Authorization

  • Node(Kubelet)の権限管理をより厳密に行う機能
  • NodeはそのNodeが関連するオブジェクトのみ権限が制限されるようになる
    • 例えば、Nodeは割り当てられたPodが参照するSecret以外にはアクセスできなくなる(Nodeのクレデンシャルが漏洩した時のリスクを最小化できる)
  • 権限は以下のように制限される(認可フェーズ→Admission Controlのフェーズ)
    • 認可(Node Authorizer)による機能
      • そのNodeに紐づくPodが参照しているSecret,ConfigMap,PersistentVolume,PersistentVolumeClaimのみ参照できる
    • Admissoon Control(Node Restriction)による機能
      • そのNodeに紐づくNodeオブジェクトのみを変更できる
      • そのNodeに紐づくPodオブジェクトのみステータスを変更できる
      • そのNodeに紐づくStaticなPodオブジェクト(mirror pod)のみを作成・更新できる
      • mirror podはSecretなどの他のオブジェクトの参照はできないようになっている
  • 個々のWorkerNodeからAPI Serverに対してAPIリクエストを認証をする際に、
    • Kubeletはsystem:nodesgroupの中のsystem:node:<nodeName>というユーザ名で認証されるように、証明書を作成する必要がある
      • 簡単に言うと、クライアント証明書のCNをsystem:node:${instance}にする必要がある

Kubernetes APIサーバ用証明書

  • k8s-the-hard-wayのstaticIPアドレスは、k8sAPIサーバの証明書のSANのリストに含める必要がある
    • SANとは、ひとつの証明書に複数のホスト名を登録することで、異なるFQDNでも1枚のSSLサーバ証明書でSSLの機能を実装できる
    • 外部のクライアントでも証明書を使った検証をおこなうため

Service Accountのキーペア

  • Kubernetes Controller Managerは、サービスアカウントのトークンの生成と署名をするためにキーペアが必要

コマンド実行メモ

  • 認証局(CA)をプロビジョニングする
    • CA設定ファイル・証明書・秘密鍵を生成する(ca.pem/ca-key.pem)
cat > ca-config.json <<EOF
{
  "signing": {
    "default": {
      "expiry": "8760h"
    },
    "profiles": {
      "kubernetes": {
        "usages": ["signing", "key encipherment", "server auth", "client auth"],
        "expiry": "8760h"
      }
    }
  }
}
EOF
cat > ca-csr.json <<EOF
{
  "CN": "Kubernetes",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "Kubernetes",
      "OU": "CA",
      "ST": "Oregon"
    }
  ]
}
EOF
cfssl gencert -initca ca-csr.json | cfssljson -bare ca
  • Worker Nodeごとに、Node Authorizerの要求を満たすクライアント証明書と秘密鍵を生成する
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
  • kube-proxyクライアント用に証明書と秘密鍵を生成する
{

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

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

}
  • kube-schedulerクライアント用の証明書と秘密鍵を生成する
{

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

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

}
  • Kubernetes API サーバーの証明書と秘密鍵を生成する
{

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

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.default \
  -profile=kubernetes \
  kubernetes-csr.json | cfssljson -bare kubernetes

}
  • service-accountの証明書と秘密鍵を発行します。
{

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

}
  • Worker Nodeに証明書と秘密鍵を配置
for instance in worker-0 worker-1 worker-2; do
  gcloud compute scp ca.pem ${instance}-key.pem ${instance}.pem ${instance}:~/
done
  • Control Planeに証明書と秘密鍵を配置
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

認証用のKubernetesの設定ファイルを生成する

概要

  • Kubernetes APIサーバが、Kubernetsのクライアントを認証出来るようにする為の、kubeconfigを生成する
    • k8sの各コンポーネント(controller-manager,kubelet,kube-proxy,scheduler,adminユーザ)は、Kubernetes APIサーバにアクセスする為に、kubeconfigを使用する
  • ``kubectl config`のset系には以下の3種類のサブコマンドがある
  set-cluster     Sets a cluster entry in kubeconfig
  set-context     Sets a context entry in kubeconfig
  set-credentials Sets a user entry in kubeconfig

kubeletのkubeconfigの例

  • kubelet -> api serverにアクセスする為のkubeconfigを作成する。
    • kubeletのノード名と同じクライアント証明書を使用する必要がある
    • そうすると、kubeletがKubernetesのNode Authorizerによって認可されるようになる
    • なので、kubelet用のkubeconfigだけノードごとにバラバラに作成する

kubectl config set-cluster

  • --kubeconfig=${instance}.kubeconfig
    • 別のconfigファイルに設定値を書き込む
  • --embed-certs=true
    • クライアント証明書・キーをkubeconfigに埋め込む
  • --certificate-authority
    • CA証明書のファイルパスを指定する
  • server
    • アクセス先のクラスタのURLを指定する

kubeconfigの配布

  • kubeletkube-proxyのkubeconfigは、各ワーカーノードにコピーする
  • kube-controller-managerkube-shcedulerのkubeconfigは、各コントローラインスタンスにコピーする

コマンド実行メモ

  • api serverの外部IPアドレスを環境変数にセット
KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \
  --region $(gcloud config get-value compute/region) \
  --format 'value(address)')
  • kubelet用のkubeconfig作成
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
  • kube-proxy用のkubeconfig作成
{
  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
}
  • kube-controller-manager用のkubeconfig作成
{
  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
}
  • kube-scheduler用のkubeconfig作成
{
  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
}
  • adminユーザ用のkubeconfigを作成
{
  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
}
  • kubeconfigを配布する
for instance in controller-0 controller-1 controller-2; do
  gcloud compute scp admin.kubeconfig kube-controller-manager.kubeconfig kube-scheduler.kubeconfig ${instance}:~/
done

データ暗号化用の設定と暗号化鍵の生成

概要

  • Kubernetesは、クラスタの状態、アプリケーションの設定、秘匿情報などを含む様々なデータが格納される
  • クラスタ内で保持しているデータを暗号化する機能が提供されている
    • 以前はetcdにSecretの情報をplain-textで保存していた
      • Node Authorization機能により、Nodeは割当らてたPodが参照するSecret以外にはアクセスできない(参考
      • 1.13からGA
  • 暗号化の設定をした

暗号化の設定

  • kube-apiserverは起動時の--encryption-provider-configで設定したconfigによりetcd内でどのように暗号化されるかを制御する。
  • 以下が公式Docの例
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
    - secrets
    providers:
    - identity: {}
    - aesgcm:
        keys:
        - name: key1
          secret: c2VjcmV0IGlzIHNlY3VyZQ==
        - name: key2
          secret: dGhpcyBpcyBwYXNzd29yZA==
    - aescbc:
        keys:
        - name: key1
          secret: c2VjcmV0IGlzIHNlY3VyZQ==
        - name: key2
          secret: dGhpcyBpcyBwYXNzd29yZA==
    - secretbox:
        keys:
        - name: key1
          secret: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=
  • resources.resourcesは、暗号化する必要があるKubernetesのリソース名の配列
  • providersは、考えられる暗号化プロバイダの順序付きリスト
    • 一つのprovider typeのみがentry毎に指定される
    • listの最初のproviderは、storageリソースを暗号化する時に使われる
    • storageからリソースを読み込む時、格納されたデータと一致する各providerは順番にデータを復号化しようとする(全て駄目ならエラーを返す)

コマンド実行メモ

  • 暗号化に用いるキーを生成
ENCRYPTION_KEY=$(head -c 32 /dev/urandom | base64)
  • 暗号化設定のconfigを生成
cat > encryption-config.yaml <<EOF
kind: EncryptionConfig
apiVersion: v1
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: ${ENCRYPTION_KEY}
      - identity: {}
EOF
  • encryption-config.yamlをコントローラインスタンスにコピー
for instance in controller-0 controller-1 controller-2; do
  gcloud compute scp encryption-config.yaml ${instance}:~/
done

etcdクラスタの起動

  • Kubernetesの各コンポーネントはステートレスで、ectdに格納され管理されている

概要

  • etcdinstall
  • /etc/etcd直下に以下を配置した
    • ca.pem(ルートCAの証明書)
    • kubernetes-key.pem(Kubernetes APIサーバ用の秘密鍵)
    • kubernetes.pem(Kubernetes APIサーバ用のクライアント証明書)
  • etcdクラスタ間で通信するために、インスタンスの内部IPアドレスを取得する
  • 各etcdのメンバは、etcdクラスタ内で、名前をユニークにする必要がある為、ホスト名でetcdの名前を設定する

コマンド実行メモ

各コントローラインスタンスで、以下を実行

  • ectdのダウンロードとインストール
wget -q --show-progress --https-only --timestamping \
  "https://github.com/coreos/etcd/releases/download/v3.3.9/etcd-v3.3.9-linux-amd64.tar.gz"
  • etcdサーバとetcdctlコマンドラインユーティリティを取り出す
tar -xvf etcd-v3.3.9-linux-amd64.tar.gz
sudo mv etcd-v3.3.9-linux-amd64/etcd* /usr/local/bin/
  • etcdサーバの設定
sudo mkdir -p /etc/etcd /var/lib/etc
sudo cp ca.pem kubernetes-key.pem kubernetes.pem /etc/etcd/
  • インスタンスの内部IPアドレス&ホスト名を取得
INTERNAL_IP=$(curl -s -H "Metadata-Flavor: Google" \
>   http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ip)
ETCD_NAME=$(hostname -s)
  • etcd.serviceとして、systemdのユニットファイルを作成
cat <<EOF | sudo tee /etc/systemd/system/etcd.service
[Unit]
Description=etcd
Documentation=https://github.com/coreos

[Service]
ExecStart=/usr/local/bin/etcd \\
  --name ${ETCD_NAME} \\
  --cert-file=/etc/etcd/kubernetes.pem \\
  --key-file=/etc/etcd/kubernetes-key.pem \\
  --peer-cert-file=/etc/etcd/kubernetes.pem \\
  --peer-key-file=/etc/etcd/kubernetes-key.pem \\
  --trusted-ca-file=/etc/etcd/ca.pem \\
  --peer-trusted-ca-file=/etc/etcd/ca.pem \\
  --peer-client-cert-auth \\
  --client-cert-auth \\
  --initial-advertise-peer-urls https://${INTERNAL_IP}:2380 \\
  --listen-peer-urls https://${INTERNAL_IP}:2380 \\
  --listen-client-urls https://${INTERNAL_IP}:2379,https://127.0.0.1:2379 \\
  --advertise-client-urls https://${INTERNAL_IP}:2379 \\
  --initial-cluster-token etcd-cluster-0 \\
  --initial-cluster controller-0=https://10.240.0.10:2380,controller-1=https://10.240.0.11:2380,controller-2=https://10.240.0.12:2380 \\
  --initial-cluster-state new \\
  --data-dir=/var/lib/etcd
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF
  • etcdサーバの起動
sudo systemctl daemon-reload
sudo systemctl enable etcd
sudo systemctl start etcd
  • 確認
    • ETCDCTL_API=3はv3のAPIを使用する設定(kubernetesの場合はv3なので基本的にここはMUST)
sudo ETCDCTL_API=3 etcdctl member list \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/etcd/ca.pem \
  --cert=/etc/etcd/kubernetes.pem \
  --key=/etc/etcd/kubernetes-key.pem

Kubernetesのコントロールパネルのブートストラップ

概要

  • kube-apiserver,kube-controller-manager,kube-schedulerをのバイナリを公式からDLしてインストールする
  • kube-apiserverを外部クライアントに公開する外部LBを作成する

KubernetesAPIサーバの設定

  • 各種証明書・鍵を/var/lib/kubernetes/に配置する
  • APIサーバをクラスタのメンバに知らせる為の設定として、インスタンスの内部IPアドレスを使うので、取得する
  • systemdの起動Unitを作成

Kubernetes Controller Managerの設定

  • controller-manager用のkubeconfigを、/var/lib/kubernetesに配置する
  • systemdの起動Unitを作成

Kubernetes schedulerの設定

  • kube-scheduler.yamlを作る
  • systemdの起動Unitを作成

HTTPヘルスチェックを有効にする

  • GCLBからのヘルスチェックがHTTPしか対応してないので、nginxを入れて200OKを無理やり返すようにする

kubelet認可のRBAC

  • kube-apiserverが、各Woerker Nodeのkubelet APIにアクセスできるように、RBACによるアクセス許可設定をする
    • メトリクスやログの取得、Pod内でのコマンドの実行には、kubelet APIへのアクセスが必要
  • kube-apiserver-to-kubeletというClusterRoleを作る
    • kubelet APIにアクセスして、Podの管理に関連するタスクを実行する権限を付与
  • kube-apiserverは--kubelet-client-certificateで定義したクライアント証明書を使って、kubernetesユーザとしてkubeletに認証をおこなう
  • system:kube-apiserver-to-kubeletのClusterRoleをkubernetesユーザにバインドする

外部LB

  • kubernetes-apiserverのフロントに置く外部LBを作成する

コマンド実行メモ

Control Planeの設定

  • 設定ファイルを配置するディレクトリを作成
sudo mkdir -p /etc/kubernetes/config
  • バイナリをダウンロードする
wget -q --show-progress --https-only --timestamping \
  "https://storage.googleapis.com/kubernetes-release/release/v1.12.0/bin/linux/amd64/kube-apiserver" \
  "https://storage.googleapis.com/kubernetes-release/release/v1.12.0/bin/linux/amd64/kube-controller-manager" \
  "https://storage.googleapis.com/kubernetes-release/release/v1.12.0/bin/linux/amd64/kube-scheduler" \
  "https://storage.googleapis.com/kubernetes-release/release/v1.12.0/bin/linux/amd64/kubectl"
  • バイナリをインストールする
chmod +x kube-apiserver kube-controller-manager kube-scheduler kubectl
sudo mv kube-apiserver kube-controller-manager kube-scheduler kubectl /usr/local/bin/
  • 秘密鍵・クライアント証明書及び暗号化のconfを/var/lib/kubernetes/に配置
sudo mkdir -p /var/lib/kubernetes/
sudo mv ca.pem ca-key.pem kubernetes-key.pem kubernetes.pem \
  service-account-key.pem service-account.pem \
  encryption-config.yaml /var/lib/kubernetes/
  • インスタンスの内部IPアドレスを取得
INTERNAL_IP=$(curl -s -H "Metadata-Flavor: Google" \
  http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ip)
  • kube-apiserver.serviceのsystemdのユニットファイルを生成
cat <<EOF | sudo tee /etc/systemd/system/kube-apiserver.service
[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-apiserver \\
  --advertise-address=${INTERNAL_IP} \\
  --allow-privileged=true \\
  --apiserver-count=3 \\
  --audit-log-maxage=30 \\
  --audit-log-maxbackup=3 \\
  --audit-log-maxsize=100 \\
  --audit-log-path=/var/log/audit.log \\
  --authorization-mode=Node,RBAC \\
  --bind-address=0.0.0.0 \\
  --client-ca-file=/var/lib/kubernetes/ca.pem \\
  --enable-admission-plugins=Initializers,NamespaceLifecycle,NodeRestriction,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota \\
  --enable-swagger-ui=true \\
  --etcd-cafile=/var/lib/kubernetes/ca.pem \\
  --etcd-certfile=/var/lib/kubernetes/kubernetes.pem \\
  --etcd-keyfile=/var/lib/kubernetes/kubernetes-key.pem \\
  --etcd-servers=https://10.240.0.10:2379,https://10.240.0.11:2379,https://10.240.0.12:2379 \\
  --event-ttl=1h \\
  --experimental-encryption-provider-config=/var/lib/kubernetes/encryption-config.yaml \\
  --kubelet-certificate-authority=/var/lib/kubernetes/ca.pem \\
  --kubelet-client-certificate=/var/lib/kubernetes/kubernetes.pem \\
  --kubelet-client-key=/var/lib/kubernetes/kubernetes-key.pem \\
  --kubelet-https=true \\
  --runtime-config=api/all \\
  --service-account-key-file=/var/lib/kubernetes/service-account.pem \\
  --service-cluster-ip-range=10.32.0.0/24 \\
  --service-node-port-range=30000-32767 \\
  --tls-cert-file=/var/lib/kubernetes/kubernetes.pem \\
  --tls-private-key-file=/var/lib/kubernetes/kubernetes-key.pem \\
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF
  • kube-controller-managerのkubeconfigを配置
sudo mv kube-controller-manager.kubeconfig /var/lib/kubernetes/
  • kube-controller-manager.serviceのsystemdユニットファイルを生成
cat <<EOF | sudo tee /etc/systemd/system/kube-controller-manager.service
[Unit]
Description=Kubernetes Controller Manager
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-controller-manager \\
  --address=0.0.0.0 \\
  --cluster-cidr=10.200.0.0/16 \\
  --cluster-name=kubernetes \\
  --cluster-signing-cert-file=/var/lib/kubernetes/ca.pem \\
  --cluster-signing-key-file=/var/lib/kubernetes/ca-key.pem \\
  --kubeconfig=/var/lib/kubernetes/kube-controller-manager.kubeconfig \\
  --leader-elect=true \\
  --root-ca-file=/var/lib/kubernetes/ca.pem \\
  --service-account-private-key-file=/var/lib/kubernetes/service-account-key.pem \\
  --service-cluster-ip-range=10.32.0.0/24 \\
  --use-service-account-credentials=true \\
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF
  • kube-schedulerのkubeconfigを生成
sudo mv kube-scheduler.kubeconfig /var/lib/kubernetes/
  • kube-scheduler.yamlを生成
cat <<EOF | sudo tee /etc/kubernetes/config/kube-scheduler.yaml
apiVersion: componentconfig/v1alpha1
kind: KubeSchedulerConfiguration
clientConnection:
  kubeconfig: "/var/lib/kubernetes/kube-scheduler.kubeconfig"
leaderElection:
  leaderElect: true
EOF
  • kube-scheduler.serviceのsystemdユニットファイルを生成
cat <<EOF | sudo tee /etc/systemd/system/kube-scheduler.service
[Unit]
Description=Kubernetes Scheduler
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-scheduler \\
  --config=/etc/kubernetes/config/kube-scheduler.yaml \\
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF
  • プロセス起動
sudo systemctl daemon-reload
sudo systemctl enable kube-apiserver kube-controller-manager kube-scheduler
sudo systemctl start kube-apiserver kube-controller-manager kube-scheduler

HTTPヘルスチェックを有効にする

  • nginx install
sudo apt-get install -y nginx
  • config生成
cat > kubernetes.default.svc.cluster.local <<EOF
server {
  listen      80;
  server_name kubernetes.default.svc.cluster.local;

  location /healthz {
     proxy_pass                    https://127.0.0.1:6443/healthz;
     proxy_ssl_trusted_certificate /var/lib/kubernetes/ca.pem;
  }
}
EOF
  sudo mv kubernetes.default.svc.cluster.local \
    /etc/nginx/sites-available/kubernetes.default.svc.cluster.local

  sudo ln -s /etc/nginx/sites-available/kubernetes.default.svc.cluster.local /etc/nginx/sites-enabled/
  • nginx reload
sudo systemctl restart nginx
sudo systemctl enable nginx

kubelet認可のRBAC

cat <<EOF | kubectl apply --kubeconfig admin.kubeconfig -f -
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: system:kube-apiserver-to-kubelet
rules:
  - apiGroups:
      - ""
    resources:
      - nodes/proxy
      - nodes/stats
      - nodes/log
      - nodes/spec
      - nodes/metrics
    verbs:
      - "*"
EOF
cat <<EOF | kubectl apply --kubeconfig admin.kubeconfig -f -
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: system:kube-apiserver
  namespace: ""
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:kube-apiserver-to-kubelet
subjects:
  - apiGroup: rbac.authorization.k8s.io
    kind: User
    name: kubernetes
EOF

外部LB

  • 外部向けLBを作成
{
  KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \
    --region $(gcloud config get-value compute/region) \
    --format 'value(address)')

  gcloud compute http-health-checks create kubernetes \
    --description "Kubernetes Health Check" \
    --host "kubernetes.default.svc.cluster.local" \
    --request-path "/healthz"

  gcloud compute firewall-rules create kubernetes-the-hard-way-allow-health-check \
    --network kubernetes-the-hard-way \
    --source-ranges 209.85.152.0/22,209.85.204.0/22,35.191.0.0/16 \
    --allow tcp

  gcloud compute target-pools create kubernetes-target-pool \
    --http-health-check kubernetes

  gcloud compute target-pools add-instances kubernetes-target-pool \
   --instances controller-0,controller-1,controller-2

  gcloud compute forwarding-rules create kubernetes-forwarding-rule \
    --address ${KUBERNETES_PUBLIC_ADDRESS} \
    --ports 6443 \
    --region $(gcloud config get-value compute/region) \
    --target-pool kubernetes-target-pool
}
  • 確認
KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \
  --region $(gcloud config get-value compute/region) \
  --format 'value(address)')
curl --cacert ca.pem https://${KUBERNETES_PUBLIC_ADDRESS}:6443/version

Kubernetesのワーカーノードをブートストラップする

概要

  • socatとは
    • 一言で言えば、proxyツール、入出力にファイル・標準入出力・コマンド・他のマシンなど、いろいろな種類を割り当てられる
    • kubectl port-forwardの為に必要

コマンド実行メモ

  • ライブラリのinstall(socatはkubectl port-forwardに必要)
sudo apt-get update
sudo apt-get -y install socat conntrack ipset
  • バイナリを落としてくる
wget -q --show-progress --https-only --timestamping \
  https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.12.0/crictl-v1.12.0-linux-amd64.tar.gz \
  https://storage.googleapis.com/kubernetes-the-hard-way/runsc-50c283b9f56bb7200938d9e207355f05f79f0d17 \
  https://github.com/opencontainers/runc/releases/download/v1.0.0-rc5/runc.amd64 \
  https://github.com/containernetworking/plugins/releases/download/v0.6.0/cni-plugins-amd64-v0.6.0.tgz \
  https://github.com/containerd/containerd/releases/download/v1.2.0-rc.0/containerd-1.2.0-rc.0.linux-amd64.tar.gz \
  https://storage.googleapis.com/kubernetes-release/release/v1.12.0/bin/linux/amd64/kubectl \
  https://storage.googleapis.com/kubernetes-release/release/v1.12.0/bin/linux/amd64/kube-proxy \
  https://storage.googleapis.com/kubernetes-release/release/v1.12.0/bin/linux/amd64/kubelet
  • インストール先のディレクトリを作成
sudo mkdir -p \
  /etc/cni/net.d \
  /opt/cni/bin \
  /var/lib/kubelet \
  /var/lib/kube-proxy \
  /var/lib/kubernetes \
  /var/run/kubernetes
  • バイナリをインストール
sudo mv runsc-50c283b9f56bb7200938d9e207355f05f79f0d17 runsc
sudo mv runc.amd64 runc
chmod +x kubectl kube-proxy kubelet runc runsc
sudo mv kubectl kube-proxy kubelet runc runsc /usr/local/bin/
sudo tar -xvf crictl-v1.12.0-linux-amd64.tar.gz -C /usr/local/bin/
sudo tar -xvf cni-plugins-amd64-v0.6.0.tgz -C /opt/cni/bin/
sudo tar -xvf containerd-1.2.0-rc.0.linux-amd64.tar.gz -C /
  • 現在のGCEインスタンスのPodのCIDR範囲を取得
POD_CIDR=$(curl -s -H "Metadata-Flavor: Google" \
  http://metadata.google.internal/computeMetadata/v1/instance/attributes/pod-cidr)
  • bridgeネットワークの設定ファイルを作成
cat <<EOF | sudo tee /etc/cni/net.d/10-bridge.conf
{
    "cniVersion": "0.3.1",
    "name": "bridge",
    "type": "bridge",
    "bridge": "cnio0",
    "isGateway": true,
    "ipMasq": true,
    "ipam": {
        "type": "host-local",
        "ranges": [
          [{"subnet": "${POD_CIDR}"}]
        ],
        "routes": [{"dst": "0.0.0.0/0"}]
    }
}
EOF
  • loopbackネットワークの設定ファイルを作成
cat <<EOF | sudo tee /etc/cni/net.d/99-loopback.conf
{
    "cniVersion": "0.3.1",
    "type": "loopback"
}
EOF
  • containerdの設定ファイルを作る
sudo mkdir -p /etc/containerd/
cat << EOF | sudo tee /etc/containerd/config.toml
[plugins]
  [plugins.cri.containerd]
    snapshotter = "overlayfs"
    [plugins.cri.containerd.default_runtime]
      runtime_type = "io.containerd.runtime.v1.linux"
      runtime_engine = "/usr/local/bin/runc"
      runtime_root = ""
    [plugins.cri.containerd.untrusted_workload_runtime]
      runtime_type = "io.containerd.runtime.v1.linux"
      runtime_engine = "/usr/local/bin/runsc"
      runtime_root = "/run/containerd/runsc"
    [plugins.cri.containerd.gvisor]
      runtime_type = "io.containerd.runtime.v1.linux"
      runtime_engine = "/usr/local/bin/runsc"
      runtime_root = "/run/containerd/runsc"
EOF
  • containerd.servicesytemdのユニットファイルを作成
cat <<EOF | sudo tee /etc/systemd/system/containerd.service
[Unit]
Description=containerd container runtime
Documentation=https://containerd.io
After=network.target

[Service]
ExecStartPre=/sbin/modprobe overlay
ExecStart=/bin/containerd
Restart=always
RestartSec=5
Delegate=yes
KillMode=process
OOMScoreAdjust=-999
LimitNOFILE=1048576
LimitNPROC=infinity
LimitCORE=infinity

[Install]
WantedBy=multi-user.target
EOF
  • kubeletの設定
sudo mv ${HOSTNAME}-key.pem ${HOSTNAME}.pem /var/lib/kubelet/
sudo mv ${HOSTNAME}.kubeconfig /var/lib/kubelet/kubeconfig
sudo mv ca.pem /var/lib/kubernetes/
  • kubelet-config.yamlを作成
cat <<EOF | sudo tee /var/lib/kubelet/kubelet-config.yaml
kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
authentication:
  anonymous:
    enabled: false
  webhook:
    enabled: true
  x509:
    clientCAFile: "/var/lib/kubernetes/ca.pem"
authorization:
  mode: Webhook
clusterDomain: "cluster.local"
clusterDNS:
  - "10.32.0.10"
podCIDR: "${POD_CIDR}"
resolvConf: "/run/systemd/resolve/resolv.conf"
runtimeRequestTimeout: "15m"
tlsCertFile: "/var/lib/kubelet/${HOSTNAME}.pem"
tlsPrivateKeyFile: "/var/lib/kubelet/${HOSTNAME}-key.pem"
EOF
  • kubelet.servicesystemdユニットファイル作成
cat <<EOF | sudo tee /etc/systemd/system/kubelet.service
[Unit]
Description=Kubernetes Kubelet
Documentation=https://github.com/kubernetes/kubernetes
After=containerd.service
Requires=containerd.service

[Service]
ExecStart=/usr/local/bin/kubelet \\
  --config=/var/lib/kubelet/kubelet-config.yaml \\
  --container-runtime=remote \\
  --container-runtime-endpoint=unix:///var/run/containerd/containerd.sock \\
  --image-pull-progress-deadline=2m \\
  --kubeconfig=/var/lib/kubelet/kubeconfig \\
  --network-plugin=cni \\
  --register-node=true \\
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF
  • kube-proxyの設定
sudo mv kube-proxy.kubeconfig /var/lib/kube-proxy/kubeconfig
  • kube-proxy-config.yamlを作成
cat <<EOF | sudo tee /var/lib/kube-proxy/kube-proxy-config.yaml
kind: KubeProxyConfiguration
apiVersion: kubeproxy.config.k8s.io/v1alpha1
clientConnection:
  kubeconfig: "/var/lib/kube-proxy/kubeconfig"
mode: "iptables"
clusterCIDR: "10.200.0.0/16"
EOF
  • kube-proxy.serviceを作成
cat <<EOF | sudo tee /etc/systemd/system/kube-proxy.service
[Unit]
Description=Kubernetes Kube Proxy
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-proxy \\
  --config=/var/lib/kube-proxy/kube-proxy-config.yaml
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF
  • ワーカーのサービス群を起動
sudo systemctl daemon-reload
sudo systemctl enable containerd kubelet kube-proxy
sudo systemctl start containerd kubelet kube-proxy

外部からのkubectlを叩くための設定

概要

  • adminユーザのcredentialに基づいた、kubectl用のkubeconifgを作成する

ルーティング

  • ノードにスケジュールされたPodは、ノードのPodのCIDRからIPアドレスを取得する
  • 現時点では、ネットワークのルートが見つからないため、異なるノード間でのPod間通信ができない
  • ノードのPod CIDR範囲を、ノードの内部IPアドレスにマップする為の、各ワーカーノードのルートを作成する

コマンド実行メモ

  • ローカル端末から今回作成したクラスタに接続するためのkubeconfigを作成する
{
  KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \
    --region $(gcloud config get-value compute/region) \
    --format 'value(address)')

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

  kubectl config set-credentials admin \
    --client-certificate=admin.pem \
    --client-key=admin-key.pem

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

  kubectl config use-context kubernetes-the-hard-way
}
  • 確認
kubectl get componentstatuses
  • 各ワーカーインスタンスにネットワークルートを設定する
for i in 0 1 2; do
  gcloud compute routes create kubernetes-route-10-200-${i}-0-24 \
    --network kubernetes-the-hard-way \
    --next-hop-address 10.240.0.2${i} \
    --destination-range 10.200.${i}.0/24
done
  • 確認
gcloud compute routes list --filter "network: kubernetes-the-hard-way"

DNSクラスターアドオンのデプロイ

概要

  • CoreDNS
    • kube-dnsで使われるDNSソフトウェア

実行コマンドメモ

  • corednsをデプロイ
kubectl apply -f https://storage.googleapis.com/kubernetes-the-hard-way/coredns.yaml
  • 確認
kubectl get pods -l k8s-app=kube-dns -n kube-system

スモークテストとお掃除をして完了!