LoginSignup
13
15

More than 5 years have passed since last update.

手動で作ってみるKubernetesクラスタ(1.7版)

Posted at

今年も末になりました。なんだか最後の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を前提とする。

    1. 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
    
    1. 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
    
    1. 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
    
    1. kubernetes自体のルート証明書を配置する

    最後に、以下のようにしてkubernetesのrootとして設定しておきます。自己署名なので、こうしないと色々問題が出ます(多分)

    $ cp ca.crt /usr/local/share/ca-certificates/kubernetes.crt
    $ update-ca-certificates
    
    1. 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にいないと何も出来ません・・・

    1. 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
    
  1. kubelet/kube-proxyのkubeconfigを作る

    kubeletは各nodeにおいて、containerの動作を管理するミドルウェアで、kube-proxyは、kubernetes内でのネットワークをproxyするもの(らしい)です。これらのツールもkubernetesのAPIにアクセスするため、kubeconfigが必要になります。

    作る方法は大きく3つあるようです。

    1. adminのcredentialをそのまま利用する
    2. kubelet用のtoken/kubeconfigを全kubeletに利用する
    3. 各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.confkubelet.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)必要があります。

  1. 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です。
  1. kubelet

    kubeletは、kubernetesクラスタの各node上で動いている必要があります。今回は全部同じnodeに載っているので、これも起動します。

    以下のオプションを設定して起動します。ちなみにバイナリは kubernetes/server/bin/kubelet にあります。

-   &#x2013;kubeconfig=/etc/kubernetes/kubelet.conf
-   &#x2013;pod-manifest-path=/etc/kubernetes/manifests
  1. 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. 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. 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. 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. 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の構成などについても、時間があれば記述します。

13
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
15