Kubernetes(K8s)を仮想環境でHard Wayに構築してみた

はじめに


Kubernetesを詳しく理解するために、「Kubernetes The Hard Way」というチュートリアルに挑戦しました。
本家チュートリアルではGoogle Cloud Platform(GCP)の無料クレジット$300以内で、K8sクラスターを手動構築する手順が紹介されていますが、

  • 無料クレジット内で学習が終わるか分からない
  • K8sクラスター構築後に検証したいことがある
  • インプットばかりではなく、読み替える方が学習効率が高い

という点を考慮し、仮想環境に読み替えてチュートリアルを行いました。
本記事では読み替えが必要となる部分を解説しています。本家チュートリアルと横並びにしてご覧ください。
(本家チュートリアルが元々Hard Way(苦労、地道)なので、読み替えを行うとさらにHard Wayになります。)

仮想環境の概要


今回構築するKubernetesクラスタのノードのイメージは以下です。

合計7台の仮想マシンを同じネットワーク上に作成してK8sクラスタを構築します。
検証環境の要件は以下の通りです。
・メモリ:12GB程度
・ディスク容量:70GB程度
なお各ノードが同じネットワーク上にあればよいため、1台のPC上に集約させなくても大丈夫です。

やってみる


目次

  1. 前提条件
  2. クライアントツールのインストール
  3. コンピューティングリソースのプロビジョニング
  4. CAのプロビジョニングとTLS証明書の生成
  5. 認証用のKubernetes構成ファイルの生成
  6. データ暗号化構成とキーの生成
  7. etcdクラスターのブートストラップ
  8. Kubernetesコントロールプレーンのブートストラップ
  9. Kubernetesワーカーノードのブートストラップ
  10. リモートアクセス用のkubectlの構成
  11. ポッドネットワークルートのプロビジョニング
  12. DNSクラスターアドオンの展開
  13. スモークテスト
1.前提条件

本家ではGCPのアカウントの準備等、作業するための準備をしています。
こちらでは、仮想環境での構築準備を行います。

構築用ノードを構築して、作業用PCからsshで接続できるように設定します。

【構築用ノード】
・ホスト名:build-0
・cpu:1
・メモリ:512MB
・OS:Ubuntu18.04
・IPアドレス:172.16.20.201/24

また、複数のVMに対して並列してコマンドを実行することが多いため[tmux]が紹介されています。
Linuxにインストールして利用するターミナルツールのようなもので、かなり便利です。
利用しなくても構築できますが、興味がありましたら「tmux公式Github」を参考に、構築用ノードにインストールしてみてください。
こんな画面で一括操作できます。

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

構築用ノードに以下のクライアントツールをインストールします。
・cfssl, cfssljson
ノード同士やコンポーネント間で認証を行うためのキーペアを生成するツールです。
・kubectl
K8sクラスタを操作するためのツールです。クラスタに対してAPIを発行して操作します。

本家の通りにバイナリをダウンロードしてインストールします。

3.コンピューティングリソースのプロビジョニング

本家のK8sTheHardWayでは、GCPに仮想プライベートネットワークを作成して、通信の設定を行い、構築ノードから、controllerノードとworkerノードをプロビジョニングする手順です。

こちらでは仮想環境にcontrollerノードとworkerノードを構築して、構築ノードからssh接続できるように設定します。

【controllerノード】
・ホスト名:controller-[0-2]
・cpu:1
・メモリ:2GB
・OS:Ubuntu18.04
・IPアドレス:172.16.20.21[0-2]/24

【workerノード】
・ホスト名:worker-[0-2]
・cpu:1
・メモリ:1GB
・OS:Ubuntu18.04
・IPアドレス:172.16.20.22[0-2]/24

4.CAのプロビジョニングとTLS証明書の生成

cfsslを利用して構築ノードで以下のコンポーネントのTLS証明書を生成します。
etcd、kube-apiserver、kube-controller-manager、kube-scheduler、 kubelet、kube-proxy

以下のステップの作業があります。
(1)認証局
(2)クライアントとサーバの証明書の生成
(3)サービスアカウントキーペア生成

本家と読み替えが必要な個所が多いです。
特に変数に気を付けてください。

変数の設定は以下のように設定します。

  • instance=<ワーカーノードホスト名>
  • INTERNAL_IP=<IPアドレス>
  • EXTERNEL_IPは設定不要です。
  • KUBERNETES_PUBLIC_ADDRESS=172.16.20.210

また、[cfssl]コマンドのオプション[-hostname]の引数は、IPアドレスを適切に読み替えて実行するようにします。

5.認証用のKubernetes構成ファイルの生成

構築用ノードのkubectlを使用してkubernetes設定ファイルのkubeconfigファイルを生成します。
以下の変数のみ注意して設定します。

  • KUBERNETES_PUBLIC_ADDRESS=172.16.20.210
  • instance=<ワーカーノードホスト名>
6.データ暗号化構成とキーの生成

Kubernetesクラスタに保存されるクラスタデータを暗号化するための機能を設定するためのファイルを生成します。
[secret]を作成するためのyaml構造のマニフェストファイルです。

構築用ノードで本家の通り作業します。

7.etcdクラスタの構築

Kubernetesのクラスタ状態を保存するためのKVS型の分散ファイルシステム[etcd]を構築します。

全てのcontrollerノードにて作業します。
次の設定値をそれぞれの環境に合わせて設定する必要があります。

  • INTERNAL_IP=<作業中のcontrollerノードIP>
  • etcd.serviceファイルの[–initial-cluster] の引数
    <controllerノードホスト名>=https://<controllerノードIP>:2380

ここでつまづいた場合は、証明書の生成がどこか間違えてるかもしれません。

8.Kubernetesコントロールプレーンの構築

3台のマシンにてKubernetesのコントロールプレーンを構成します。
以下のコンポーネントを設定します。

  • kubernetes-api-server: K8sクラスタに対する操作を受け付けるコンポーネント
  • kubernetes-controller-manager: controllerノードで動くcontrollersを動かすコンポーネント
  • kube-scheduler: 新規作成されるポッドを稼働できるノードに割り当てるコンポーネント

次の設定値を環境に合わせて設定します。

  • INTERNAL_IP=<作業中のcontrollerノードIP>
  • kube-apiserver.serviceファイルの[–etcd-servers=]の設定値
    https://<controllerノードIP>:2379

Kubernetesのネットワーク構成や動作を理解しているとスムーズに進みます。

また、作成したk8sクラスタでclusterroleを作成し、workerノードのkubeletにアクセスできるようにします。
こちらは本家の通りに作業を行います。

9.Kubernetesワーカーノードの構築

3台のマシンにてKubernetesのworkerノードを構成します。
以下のコンポーネントを設定します。

  • runc:低レベルコンテナランタイム。実際にコンテナを管理する。
  • containerd: 高レベルコンテナランタイム。コンテナのイメージの管理や、低レベルコンテナランタイムに情報の受け渡しなどを行う。
  • cri-tools: kubeletCRIにデバッグおよび検証ツールを提供する。
  • cni プラグイン: K8sクラスタ内部のネットワークを提供する。
  • kube-proxy: K8sサービスオブジェクトが作成された際の情報を元にルーティングを行います。
  • kubelet: コンテナランタイムと連携してコンテナを管理する。

ここでは、以下の変数を環境に合わせて設定します。

  • POD_CIDR=<ポッドネットワーク>
    それぞれのworkerノードに設定するPODネットワークを設定します。
worker-0 # POD_CIDR=10.200.0.0/24
worker-1 # POD_CIDR=10.200.1.0/24
worker-2 # POD_CIDR=10.200.2.0/24

また、containerdの設定ファイルを以下のように書き換えます。

# cat /etc/containerd/config.toml
[plugins]
  [plugins.cri]                               ←ここを追加します。
  stream_server_address = "<workerノードIP>"  ←
  [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 = ""

ここまでの作業が成功すると、controllerノードからworkerノードが参照できるようになります。

# kubectl get nodes --kubeconfig admin.kubeconfig
NAME       STATUS   ROLES    AGE   VERSION
worker-0   Ready    <none>   79m   v1.15.3
worker-1   Ready    <none>   79m   v1.15.3
worker-2   Ready    <none>   79m   v1.15.3
10.リモートアクセス用のkubectl の構成

構築用ノードのkubectl でK8sクラスタにadminユーザでアクセスできるようにするために設定を行います。

本家と違い、仮想環境のKubernetesAPIの前段にLBはいないので、controller-0を宛先とします。
以下の通りです。

  • KUBERNETES_PUBLIC_ADDRESS=172.16.20.210

この設定が成功すると、構築用ノードから[kubectl]コマンドでk8sクラスタにadminロールの権限でアクセスできるようになります。

build-0# kubectl get nodes
    NAME       STATUS   ROLES    AGE   VERSION
worker-0   Ready    <none>   79m   v1.15.3
worker-1   Ready    <none>   79m   v1.15.3
worker-2   Ready    <none>   79m   v1.15.3
11.ポッドネットワークのルーティング設定

現時点では、ポッドは別のノードで実行されている他のポッドと通信できません。
本家ではGCPのVPCゲートウェイにルーティング設定を入れていますが、こちらではK8sクラスタ各ノードのOSにルーティングの設定を行います。

workerノードごとの内部IPとポッドCIDRは以下です。

ノード     内部IP        ポッドCIDR
worker-0 172.16.20.220 10.200.0.0/24
worker-1 172.16.20.221 10.200.1.0/24
worker-2 172.16.20.222 10.200.2.0/24

ポッドCIDR宛ての通信は、そのポッドCIDRを持つworkerノードの内部IPに転送するようにルーティングします。
以下のコマンドをcontrollerノード全てで実行します。
workerノードは以下のコマンドを自分自身のポッドネットワーク宛てでないもののみ実行します。

# route add -net 10.200.0.0 netmask 255.255.255.0 gw 172.16.20.220
# route add -net 10.200.1.0 netmask 255.255.255.0 gw 172.16.20.221
# route add -net 10.200.2.0 netmask 255.255.255.0 gw 172.16.20.222
12.DNSクラスタアドオンの展開

K8sクラスタ内で実行されるアプリケーションが利用する、
DNSベースのサービスディスカバリを、CoreDNSにて構築します。

本家のマニフェストをそのまま適用するだけでcorednsを設定できます。

このマニフェスト内では、名前空間[kube-system]にクラスタロール[system:coredns]を作成し、 [coredns]サービスアカウントにバインドしています。
その次に[ConfigMap]オブジェクトに、corednsの設定値を保存しています。
その後[Deployment]コントローラにてcorednsコンテナのポッドをレプリカ数2で作成しています。
最後にサービスをCoreDNSというKubernetes内で利用する[Service]オブジェクトを作成しています。

成功すると、以下の応答があります。

root@build-0:~# kubectl exec -it busybox -- nslookup kubernetes
Server:    10.32.0.10
Address 1: 10.32.0.10 kube-dns.kube-system.svc.cluster.local

Name:      kubernetes
Address 1: 10.32.0.0 kubernetes.default.svc.cluster.local
root@build-1:~#

ここで名前解決が出来ずにつまづきました。

ルーティング設定が間違っていて、pod間での通信が出来ていなかったことが原因でした。
以下の3つを確認して修正することで、うまくいきました。

1. 各ノード→ポッドのコンテナに対して、疎通ができるか。
2. 別ノード上のポッド同士で疎通ができるか。
3. 同一ノード上のポッド同士で疎通ができるか。
13.スモークテスト

K8sクラスタが正常に機能していることを簡単なテストで確認します。
それぞれの機能について、1連のタスクにてテストします。

具体的には以下の機能をテストします。

  • データ暗号化:
    [secret]に保管中の秘密データを暗号化する機能の正常性を確認します。
  • deploymentコントローラ:
    deploymentコントローラを作成、管理する機能の正常性を確認します。
  • ポート転送:
    ポートフォワーディングを使用して、リモートでアプリケーションにアクセスする機能を確認します。
  • ログ: コンテナログを取得する機能の正常性を確認します。
  • 実行: コンテナでコマンドを実行する機能の正常性を確認します。
  • サービス:
    サービスを利用してアプリケーションを公開する機能の正常性を確認します。

本家そのまま実行して成功するので、もし何かうまくいかない場合は他の設定を見直してみてください。

おわりに


険しい道を選んだ分、うまく構築できた時の感動はとても味わい深いものです。
本家では、構築したクラスタをすぐに削除しないと料金がかかりますが、絶対に消したくないと思いました。

今回構築したK8sクラスタをもとにして、さらなる学習・実験を行おうと思っています。
・Workerノード追加、削除
・物理Workerノード追加
などなど随時記事を追加・更新していきますので、お楽しみに。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です