今年も末になりました。なんだか最後の3ヶ月くらいが随分ドタバタしてたなぁってくらいしか、現場での記憶がないのはどうしたことか・・・。
今の現場では、どちらかと言うとレガシーよりのアーキテクチャ+AWSも自由がない、というところで、中々新しいことを実験したりするのが難しいです。
しかし、なんか新しいこと(自分にとって)をやりたいなーって思っていたところ、今年のRe:Inventで AWS上でのManaged Kubernetes が発表されました。
Google Cloud PlatformのGKEは前から知っていたし、Kubernetesというのがコンテナクラスタ管理のデファクトになったということも聞いていました。興味はあったんですが、どうにも食指が動かず・・・。
しかし、AWS上で KubernetesのManaged Service が出来たことで、仕事で利用する可能性も上がり、かつ最近Dockerを結構触っていることもあり、コンテナの管理に興味がでたところだったので、kubernetesを触ってみることにしました。
Tutorialが普通じゃない
大抵、こういう管理ツールは構築が難しい(installationがやたら長い)のがよくありますが、Kubernetesのsetupを見た時は驚きました。
何が驚いたかって、まず最初っから手動での構築はやめとけと言わんばかりに、Managed Serviceや構築済みの環境https://github.com/kubernetes/minikube を勧めてきます。
しかし、個人的にはこういうものは作りながら仕組みを知りたい派閥なのと、勉強になるだろーってことで、 スクラッチで構築してみる ことにしました。
ここからは、実際に手を動かしてKubernetesを構築してみた履歴です。
なお、後からぐぐったところ、同じようなことをやっている人がいて、 Hard Way と書かれていました。実際に体験すると、もうほんとにHard Wayだということがよくわかりました・・・。みんなManaged Serviceを欲しがるわけだ。
手動セットアップの履歴
今回行うセットアップは、以下を前提とします。
- Ubuntu 16.04上
- 単純に困った時に情報量が多いってだけです
- Kubernetes 1.7
- やってる間に1.8が正式リリースされたようです
今回はとりあえず動けばいいため、Vagrant + Virtualboxの組み合わせの上で動かしました。
では行ってみましょう。
1. Virtualboxで仮想マシンをたてる
前述の仮想マシンを立てます。Vagrantでの条件としては以下を満たしていればOKなようでした。
-
2core/2GB
- etcd/kube-proxyの動作に1core/1GBのメモリが必要
- これくらいで30個くらいのコンテナを制御できるらしいです
- host only network
- 後述するmaster nodeの設定のために必要です。無いとネットワーク設定がものすごく面倒になります。
2.最低限のネットワーク設定
クラスタのIPレンジを決めておく必要があります。今回は 10.1.0.0/16
とします。
さらに、以下を決めておく必要があります。
- kubectlからアクセスするmaster nodeのIP
- 今回はVagrant自身なのですが、これをhost only networkのIPにしておきます
- MASTERIPはローカルホスト以外である必要があるので
- 80/443が開いている
- sysctlでnet.ipv4.ipforward = 1
以降では、master nodeのIPは MASTER_IP
という環境変数に入れたことにして進めます。ファイル内とかで MASTER_IP
があったらmaster nodeのIPということです。
3.最低限の事前設定
先に挙げた参考ページでは、 /srv/kubernetes
を利用していましたが、kubeadmというツールで作成されるものが /etc/kubernetes
に作成されることと、通常は /etc
が設定ファイル類なので、ここに作成します。
\# mkdir -p /etc/kubernetes
4.master nodeに対してソフトウェアをインストールする
ここからが本番です。Kubernetesのmaster nodeには、以下のソフトウェアが必要とされています。
- etcd
- ただし、container runner上で動かすのが奨励されていたので、aptとかでは入れません
- container runner
- docker/rktといったものを導入できますが、ここではdockerにしておきます
-
Kubernetesの各種ツール
全部まとめてgithubにreleaseから取得できます。今回はv1.7.11を使います。
https://github.com/kubernetes/kubernetes/releases/tag実際には、以下のツールを利用します。全部まとめてダウンロードされてくるので心配ありません。
- kube-apiserver
- kubelet
- kube-proxy
- kube-controller-manager
- kube-scheduler
以下のようにして、まとめて導入します。なお、これ以降は基本的にrootで作業するのがおすすめです。
# Dockerの導入はhttps://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/#set-up-the-repositoryを参考
# Kubernetesのバイナリをダウンロード。450MB(!)くらいあります
$ curl -LO https://github.com/kubernetes/kubernetes/releases/tag/v1.17.11
$ tar xf kubernetes.tar.gz
$ ./kubernetes/cluster/get-kube-binaries.sh
$ tar xf kubernetes/client/kubernetes-client-linux-amd64.tar.gz
$ tar xf kubernetes/server/kubernetes-server-linux-amd64.tar.gz
# これで、 kubernetes/client/bin と kubernetes/server/bin にそれぞれバイナリが入ります
5.ミドルウェアのdocker imageを作る
kube-apiserver/kube-controller-manager/kube-scheduler/etcdについては、docker pullやkubernetesの実行時に持ってきたり出来るようですが、自前でも作成できます。今回は自前で作成します。
$ docker image load -i kubernetes/server/bin/kube-apiserver.tar
$ docker image load -i kubernetes/server/bin/kube-scheduler.tar
$ docker image load -i kubernetes/server/bin/kube-controller-manager.tar
$ cd kubernetes/cluster/image/etcd; make
# v1.17.11ではこれが普通に動かない・・・ということで諦めて普通にpullしました
$ docker image pull gcr.io/google-containers/etcd:3.0.17
6.セキュリティモデルと事前設定
httpでの接続も利用できるようですが、基本的に全部httpsにするのが推奨されていたので、面倒ですが色々と行います。
最終的にはhttpsで接続しないといけないので、面倒ではあるがhttpsを前提とする。
-
- CAを作る
作る場所は
/etc/kubernetes/pki
にします。まずは必要なprivate keyとcrtを作成します。# ca.keyを生成 $ openssl genrsa -out ca.key 2048 # ca.crtを生成 $ openssl req -x509 -new -nodes -key ca.key -subj "/CN=${MASTER_IP}" -days 10000 -out ca.crt # server.keyを生成 $ openssl genrsa -out server.key 2048
-
- Certificate Signed Request(CSR)の設定を作成する
以下の内容のファイルを、
/etc/kubernetes/pki/csr.conf
として作成します。ファイル内のMASTERCLUSTERIPは、最初に決めたクラスタのIPレンジにおける、最初のアドレスです。今回の場合であれば10.1.0.1/32
になります。
–service-cluster-ip-rangeの最初のIPアドレス。–service-cluster-ip-rangeはapiserverのオプション。[ req ] default_bits = 2048 prompt = no default_md = sha256 req_extensions = req_ext distinguished_name = dn [ dn ] C = <country> ST = <state> L = <city> O = <organization> OU = <organization unit> CN = <MASTER_IP> [ req_ext ] subjectAltName = @alt_names [ alt_names ] DNS.1 = kubernetes DNS.2 = kubernetes.default DNS.3 = kubernetes.default.svc DNS.4 = kubernetes.default.svc.cluster DNS.5 = kubernetes.default.svc.cluster.local IP.1 = <MASTER_IP> IP.2 = <MASTER_CLUSTER_IP> [ v3_ext ] authorityKeyIdentifier=keyid,issuer:always basicConstraints=CA:FALSE keyUsage=keyEncipherment,dataEncipherment extendedKeyUsage=serverAuth,clientAuth subjectAltName=@alt_names
-
- server.keyにCSRを適用してx509を生成する
作成したCSRを適用します。正直この辺は知識が無くて何をやっているかよくわからなかったです・・・。
$ openssl req -new -key server.key -out server.csr -config csr.conf # Server certificateを生成する $ openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \ -CAcreateserial -out server.crt -days 10000 \ -extensions v3_ext -extfile csr.conf # server certificateを表示する。これをそれぞれの設定におけるcertificateとして利用する $ openssl x509 -noout -text -in ./server.crt
-
- kubernetes自体のルート証明書を配置する
最後に、以下のようにしてkubernetesのrootとして設定しておきます。自己署名なので、こうしないと色々問題が出ます(多分)
$ cp ca.crt /usr/local/share/ca-certificates/kubernetes.crt $ update-ca-certificates
-
- Credentialを設定する
Kubernetesでは、Production readyということもあり、様々な認証方式が利用できます。user/passwordや、各ユーザーごとに証明書を作成して登録、とかも出来るようです。
ここでは簡単にやるためにtokenを使います。
作ったtokenは、
/etc/kube-apiserver/known_tokens.csv
に保存します。knowntokens.csvの中身は次のようなフォーマットです。token,user,uid,"group1,group2,group3" $ TOKEN=$(dd if=/dev/urandom bs=128 count=1 2>/dev/null | base64 | tr -d "=+/" | dd bs=32 count=1 2>/dev/null) $ echo "$TOKEN,admin,admin,\"system:masters\"" > /etc/kube-apiserver/known_tokens.csv
ここで指定system:mastersというのは、Kubernetesにデフォルトで用意されている、管理用のgroupのようです。このgroupにいないと何も出来ません・・・
-
- credentialをclientに公開する
credentialは、kubeconfigとしてclientから利用できる形式にしないと、kubectlから制御することができません。
kubectlのset-credentialsコマンドを利用します。kubectlはどこかからダウンロードしておきます。
export CLUSTER_NAME=scratch export CA_CERT=/etc/kubernetes/pki/ca.crt export MASTER_IP=<ip> export TOKEN=<生成してknown_tokens.csvに入れたtoken> export USER=admin export CONTEXT_NAME=scratch kubectl config set-cluster $CLUSTER_NAME --certificate-authority=$CA_CERT --embed-certs=true --server=https://$MASTER_IP kubectl config set-credentials $USER --client-certificate=$CLI_CERT --client-key=$CLI_KEY --embed-certs=true --token=$TOKEN # Set your cluster as the default cluster to use: kubectl config set-context $CONTEXT_NAME --cluster=$CLUSTER_NAME --user=$USER kubectl config use-context $CONTEXT_NAME
-
kubelet/kube-proxyのkubeconfigを作る
kubeletは各nodeにおいて、containerの動作を管理するミドルウェアで、kube-proxyは、kubernetes内でのネットワークをproxyするもの(らしい)です。これらのツールもkubernetesのAPIにアクセスするため、kubeconfigが必要になります。
作る方法は大きく3つあるようです。
- adminのcredentialをそのまま利用する
- kubelet用のtoken/kubeconfigを全kubeletに利用する
- 各kubeletごとに作成する(現状すごいめんどくさい
今回はsimplestな1.を利用します。なお、MASTERIPの後についている6443ポートは、kube-apiserverのポートです。後で出てきます。
apiVersion: v1 kind: Config users: - name: kubelet user: token: <token> clusters: - name: local cluster: certificate-authority: /etc/kubernetes/pki/ca.crt server: ${MASTER_IP}:6443 contexts: - context: cluster: local user: kubelet name: service-account-context current-context: service-account-context
こんな感じのkubeconfigを、
/etc/kubernetes
に、kube-proxy.conf
とkubelet.conf
として保存します。
7.ネットワークの設定
kubernetesではブリッジを利用します。dockerをインストールすると、自動的に docker0
というブリッジが作られますが、これは邪魔になるので削除しておきましょう。
cbr0という名前のbridgeが必要なので、以下のようにして作成します。ブリッジに設定するアドレスは、clusterのアドレス空間の1つめのアドレスでなければならないので注意です。
# ブリッジを作成する
$ ip link add name cbr0 type bridge
# MTUを設定する
$ ip link set dev cbr0 mtu 1460
# bridgeのアドレスを作成する
$ ip addr add 10.1.0.1/16 dev cbr0
# 有効にする
$ ip link set dev cbr0 up
8.master nodeで必要なミドルウェアの設定と起動
最低限これを起動しないとならないです。全部root権限でやる(dockerはsystemctl)必要があります。
-
docker
今回のように単体でインストールされている場合、当然ですがkubernetes特有のオプション設定などはされていません。docker0というブリッジが作成されているので、先に削除しておく必要があります。
$ iptables -t nat -F $ ip link set docker0 down $ ip link delete docker0
Dockerのオプションとして以下の設定が必要です。systemctlを利用している場合は、以下と等価な内容を
/etc/docker/daemon.json
に書く必要があります。
- `--bridge=cbr0`
- `--iptables=false`
- `--ip-masq=false`
- 環境に依存するので要注意
- `--mtu=`
- Flannelを利用する場合は必要
- `--insecure-registry $CLUSTER_SUBNET`
- private registryを利用する場合で、すでにあるregistryがhttpの場合
systemctlからDockerを再起動して、普通に起動したらOKです。
-
kubelet
kubeletは、kubernetesクラスタの各node上で動いている必要があります。今回は全部同じnodeに載っているので、これも起動します。
以下のオプションを設定して起動します。ちなみにバイナリは
kubernetes/server/bin/kubelet
にあります。
- –kubeconfig=/etc/kubernetes/kubelet.conf
- –pod-manifest-path=/etc/kubernetes/manifests
-
kube-proxy
kube-proxyは、v1.17.1版では –config, –config-write-to、 –cleanup-iptables以外はdeprecatedになっています。参考のドキュメントとは全く異なる形で起動する必要があります。
設定ファイルを作成しないといけないのですが、デフォルトの設定は
kubernetes/server/bin/kube-proxy --write-config-to <ファイルパス>
を実行すると生成できますので、これをいじることにします。
生成されるファイル形式はyamlです。versionがalpha1とか書いているのがなんとも言えない感じがありますが・・・。apiVersion: componentconfig/v1alpha1 bindAddress: 0.0.0.0 clientConnection: acceptContentTypes: "" burst: 10 contentType: application/vnd.kubernetes.protobuf kubeconfig: /etc/kubernetes/kube-proxy.conf qps: 5 clusterCIDR: 10.1.0.0/16 configSyncPeriod: 15m0s conntrack: max: 0 maxPerCore: 32768 min: 131072 tcpCloseWaitTimeout: 1h0m0s tcpEstablishedTimeout: 24h0m0s enableProfiling: false featureGates: "" healthzBindAddress: 0.0.0.0:10256 hostnameOverride: "" iptables: masqueradeAll: false masqueradeBit: 14 minSyncPeriod: 0s syncPeriod: 30s kind: KubeProxyConfiguration metricsBindAddress: 127.0.0.1:10249 mode: "" oomScoreAdj: -999 portRange: "" resourceContainer: /kube-proxy udpTimeoutMilliseconds: 250ms
この設定の中で、 kubeconfig の設定が必須です。後は clusterCIDR も設定しないと基本動きません。kube-proxyの起動時は、
--config
オプションだけでOKです。$ kubernetes/server/bin/kube-proxy --config /etc/kubernetes/kube-proxy.yml
9.サービスのbootstrap
各サービスのmanifestは、そもそも参考ドキュメントに書いてあるパスがないという事態になりました・・・。しょうがないので、 kubeadm init
で初期のものを作成します。
それぞれの設定は =/etc/kubernetes/manifests/<サービス名>.yamlとして保存します。
これらの設定は、kubeletが起動していれば、自動的に起動が行われていきます。
-
1.etcd
apiVersion: v1 kind: Pod metadata: annotations: scheduler.alpha.kubernetes.io/critical-pod: "" creationTimestamp: null labels: component: etcd tier: control-plane name: etcd namespace: kube-system spec: containers: - command: - etcd - --listen-client-urls=http://127.0.0.1:2379 - --advertise-client-urls=http://127.0.0.1:2379 - --data-dir=/var/lib/etcd image: gcr.io/google_containers/etcd-amd64:3.0.17 livenessProbe: failureThreshold: 8 httpGet: host: 127.0.0.1 path: /health port: 2379 scheme: HTTP initialDelaySeconds: 15 timeoutSeconds: 15 name: etcd resources: {} volumeMounts: - mountPath: /etc/ssl/certs name: certs - mountPath: /var/lib/etcd name: etcd - mountPath: /etc/kubernetes name: k8s readOnly: true hostNetwork: true volumes: - hostPath: path: /etc/ssl/certs name: certs - hostPath: path: /var/lib/etcd name: etcd - hostPath: path: /etc/kubernetes name: k8s status: {}
-
2.apiserver
kubeadmで作成したapiserver.yamlは、client certとかservice account certとかも全部入りのやつなので、一旦必要最小限のオプションで起動するようにします。
特に
--advertise-address
と--service-cluster-ip-range
は必須です。apiVersion: v1 kind: Pod metadata: annotations: scheduler.alpha.kubernetes.io/critical-pod: "" creationTimestamp: null labels: component: kube-apiserver tier: control-plane name: kube-apiserver namespace: kube-system spec: containers: - command: - kube-apiserver - --secure-port=6443 - --experimental-bootstrap-token-auth=true - --requestheader-allowed-names=front-proxy-client - --token-auth-file=/etc/kubernetes/known_tokens.csv - --insecure-port=0 - --admission-control=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,ResourceQuota - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname - --requestheader-extra-headers-prefix=X-Remote-Extra- - --requestheader-username-headers=X-Remote-User - --tls-cert-file=/etc/kubernetes/pki/server.crt - --tls-private-key-file=/etc/kubernetes/pki/server.key - --tls-ca-file=/etc/kubernetes/pki/ca.crt - --allow-privileged=true - --requestheader-group-headers=X-Remote-Group - --service-cluster-ip-range=10.1.0.0/16 - --authorization-mode=Node,RBAC - --advertise-address=192.168.33.10 - --etcd-servers=http://127.0.0.1:2379 image: gcr.io/google_containers/kube-apiserver-amd64:v1.7.11 livenessProbe: failureThreshold: 8 httpGet: host: 127.0.0.1 path: /healthz port: 6443 scheme: HTTPS initialDelaySeconds: 15 timeoutSeconds: 15 name: kube-apiserver resources: requests: cpu: 250m volumeMounts: - mountPath: /etc/kubernetes name: k8s readOnly: true - mountPath: /etc/ssl/certs name: certs hostNetwork: true volumes: - hostPath: path: /etc/kubernetes name: k8s - hostPath: path: /etc/ssl/certs name: certs status: {}
-
3.scheduler
デフォルトでOKのようです。
apiVersion: v1 kind: Pod metadata: annotations: scheduler.alpha.kubernetes.io/critical-pod: "" creationTimestamp: null labels: component: kube-scheduler tier: control-plane name: kube-scheduler namespace: kube-system spec: containers: - command: - kube-scheduler - --address=127.0.0.1 - --leader-elect=true - --kubeconfig=/etc/kubernetes/scheduler.conf image: gcr.io/google_containers/kube-scheduler-amd64:v1.7.11 livenessProbe: failureThreshold: 8 httpGet: host: 127.0.0.1 path: /healthz port: 10251 scheme: HTTP initialDelaySeconds: 15 timeoutSeconds: 15 name: kube-scheduler resources: requests: cpu: 100m volumeMounts: - mountPath: /etc/kubernetes name: k8s readOnly: true hostNetwork: true volumes: - hostPath: path: /etc/kubernetes name: k8s status: {}
-
4.controller-manager
認証情報の部分以外はデフォルトで良さそうです。
apiVersion: v1 kind: Pod metadata: annotations: scheduler.alpha.kubernetes.io/critical-pod: "" creationTimestamp: null labels: component: kube-controller-manager tier: control-plane name: kube-controller-manager namespace: kube-system spec: containers: - command: - kube-controller-manager - --leader-elect=true - --kubeconfig=/etc/kubernetes/controller-manager.conf - --cluster-signing-key-file=/etc/kubernetes/pki/ca.key - --address=127.0.0.1 - --controllers=*,bootstrapsigner,tokencleaner - --root-ca-file=/etc/kubernetes/pki/ca.crt - --service-account-private-key-file=/etc/kubernetes/pki/sa.key - --cluster-signing-cert-file=/etc/kubernetes/pki/ca.crt - --use-service-account-credentials=true image: gcr.io/google_containers/kube-controller-manager-amd64:v1.7.11 livenessProbe: failureThreshold: 8 httpGet: host: 127.0.0.1 path: /healthz port: 10252 scheme: HTTP initialDelaySeconds: 15 timeoutSeconds: 15 name: kube-controller-manager resources: requests: cpu: 200m volumeMounts: - mountPath: /etc/kubernetes name: k8s readOnly: true - mountPath: /etc/ssl/certs name: certs hostNetwork: true volumes: - hostPath: path: /etc/kubernetes name: k8s - hostPath: path: /etc/ssl/certs name: certs status: {}
10.API serverが動いているか確認
curl -s https://$MASTER_IP:6443/healthz
で、okが返ってくればとりあえずはOKです。
curl -s https://$MASTER_IP:6443/api
でJSONが返ってくれば、kubectlは利用できる状態になっています。 kubectl get nodes
とかをしたとき、ちゃんとノード一覧に自身が出てこない場合、どこかでエラーが出ているはずなので、いろいろなログを見ながら頑張る必要があります。
手動セットアップのつらさ
冗長性とかを全部捨てて、最低限動作するまでを書いてみました。実際にはここから先で、Proxy/DNSの動作をするところまで確認していますが、それはまた後日ということで・・・。
手動でやってみてわかりましたが、Kubernetesは複数のミドルウェアをうまく組み合わせて構成しているという構成のため、それぞれの構成について冗長性を確保しなければならないと考えると、非常に辛いことは想像に容易いです。
これは確かにManagement serviceが欲しくなります。ただ、やはり手動で作成すると、ミドルウェアの構成であったり、ネットワークの構成であったりといったものは(いやでも)理解が進みます。
Hard wayであるにせよ、一回は利用する前・後で構いませんので、時間があるときに手動で作ってみるのはいかがでしょうか。
DNSの構成などについても、時間があれば記述します。