手動で作ってみるKubernetesクラスタ（1.7版）


今年も末になりました。なんだか最後の３ヶ月くらいが随分ドタバタしてたなぁってくらいしか、現場での記憶がないのはどうしたことか・・・。

今の現場では、どちらかと言うとレガシーよりのアーキテクチャ＋AWSも自由がない、というところで、中々新しいことを実験したりするのが難しいです。
しかし、なんか新しいこと（自分にとって）をやりたいなーって思っていたところ、今年のRe:Inventで [AWS上でのManaged Kubernetes](https://aws.amazon.com/jp/eks/) が発表されました。

Google Cloud PlatformのGKEは前から知っていたし、Kubernetesというのがコンテナクラスタ管理のデファクトになったということも聞いていました。興味はあったんですが、どうにも食指が動かず・・・。

しかし、AWS上で **KubernetesのManaged Service** が出来たことで、仕事で利用する可能性も上がり、かつ最近Dockerを結構触っていることもあり、コンテナの管理に興味がでたところだったので、kubernetesを触ってみることにしました。

## Tutorialが普通じゃない<a id="sec-1-1" name="sec-1-1"></a>

大抵、こういう管理ツールは構築が難しい（installationがやたら長い）のがよくありますが、Kubernetesのsetupを見た時は驚きました。

<https://kubernetes.io/docs/setup/pick-right-solution/>

何が驚いたかって、まず最初っから手動での構築はやめとけと言わんばかりに、Managed Serviceや構築済みの環境<https://github.com/kubernetes/minikube> を勧めてきます。

しかし、個人的にはこういうものは作りながら仕組みを知りたい派閥なのと、勉強になるだろーってことで、 [スクラッチで構築してみる](https://kubernetes.io/docs/getting-started-guides/scratch/) ことにしました。

ここからは、実際に手を動かしてKubernetesを構築してみた履歴です。

> なお、後からぐぐったところ、同じようなことをやっている人がいて、 **Hard Way** と書かれていました。実際に体験すると、もうほんとにHard Wayだということがよくわかりました・・・。みんなManaged Serviceを欲しがるわけだ。

## 手動セットアップの履歴<a id="sec-1-2" name="sec-1-2"></a>

今回行うセットアップは、以下を前提とします。

-   Ubuntu 16.04上
    -   単純に困った時に情報量が多いってだけです
-   Kubernetes 1.7
    -   やってる間に1.8が正式リリースされたようです

今回はとりあえず動けばいいため、Vagrant + Virtualboxの組み合わせの上で動かしました。

では行ってみましょう。

### 1. Virtualboxで仮想マシンをたてる<a id="sec-1-2-1" name="sec-1-2-1"></a>

前述の仮想マシンを立てます。Vagrantでの条件としては以下を満たしていればOKなようでした。

-   `2core/2GB`
    -   etcd/kube-proxyの動作に1core/1GBのメモリが必要
    -   これくらいで30個くらいのコンテナを制御できるらしいです
-   host only network
    -   後述するmaster nodeの設定のために必要です。無いとネットワーク設定がものすごく面倒になります。

### 2.最低限のネットワーク設定<a id="sec-1-2-2" name="sec-1-2-2"></a>

クラスタのIPレンジを決めておく必要があります。今回は `10.1.0.0/16` とします。

さらに、以下を決めておく必要があります。

-   kubectlからアクセスするmaster nodeのIP
    -   今回はVagrant自身なのですが、これをhost only networkのIPにしておきます
    -   MASTER<sub>IPはローカルホスト以外である必要があるので</sub>
-   80/443が開いている
-   sysctlでnet.ipv4.ip<sub>forward</sub> = 1

以降では、master nodeのIPは `MASTER_IP` という環境変数に入れたことにして進めます。ファイル内とかで `MASTER_IP` があったらmaster nodeのIPということです。

### 3.最低限の事前設定<a id="sec-1-2-3" name="sec-1-2-3"></a>

先に挙げた参考ページでは、 `/srv/kubernetes` を利用していましたが、kubeadmというツールで作成されるものが `/etc/kubernetes` に作成されることと、通常は `/etc` が設定ファイル類なので、ここに作成します。

    \# mkdir -p /etc/kubernetes

### 4.master nodeに対してソフトウェアをインストールする<a id="sec-1-2-4" name="sec-1-2-4"></a>

ここからが本番です。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を作る<a id="sec-1-2-5" name="sec-1-2-5"></a>

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.セキュリティモデルと事前設定<a id="sec-1-2-6" name="sec-1-2-6"></a>

httpでの接続も利用できるようですが、基本的に全部httpsにするのが推奨されていたので、面倒ですが色々と行います。
最終的にはhttpsで接続しないといけないので、面倒ではあるがhttpsを前提とする。

1.  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

2.  2. Certificate Signed Request(CSR)の設定を作成する

    以下の内容のファイルを、 `/etc/kubernetes/pki/csr.conf` として作成します。ファイル内のMASTER<sub>CLUSTER</sub><sub>IPは、最初に決めたクラスタのIPレンジにおける、最初のアドレスです。今回の場合であれば</sub> `10.1.0.1/32` になります。
    &#x2013;service-cluster-ip-rangeの最初のIPアドレス。&#x2013;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

3.  3. 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

4.  4. kubernetes自体のルート証明書を配置する

    最後に、以下のようにしてkubernetesのrootとして設定しておきます。自己署名なので、こうしないと色々問題が出ます（多分）
    
        $ cp ca.crt /usr/local/share/ca-certificates/kubernetes.crt
        $ update-ca-certificates

5.  5. Credentialを設定する

    Kubernetesでは、Production readyということもあり、様々な認証方式が利用できます。user/passwordや、各ユーザーごとに証明書を作成して登録、とかも出来るようです。
    
    ここでは簡単にやるためにtokenを使います。
    
    作ったtokenは、 `/etc/kube-apiserver/known_tokens.csv` に保存します。known<sub>tokens</sub>.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にいないと何も出来ません・・・

6.  6. 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

7.  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.を利用します。なお、MASTER<sub>IPの後についている6443ポートは、kube</sub>-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.ネットワークの設定<a id="sec-1-2-7" name="sec-1-2-7"></a>

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で必要なミドルウェアの設定と起動<a id="sec-1-2-8" name="sec-1-2-8"></a>

最低限これを起動しないとならないです。全部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です。

2.  kubelet

    kubeletは、kubernetesクラスタの各node上で動いている必要があります。今回は全部同じnodeに載っているので、これも起動します。
    
    以下のオプションを設定して起動します。ちなみにバイナリは `kubernetes/server/bin/kubelet` にあります。
    
    -   &#x2013;kubeconfig=/etc/kubernetes/kubelet.conf
    -   &#x2013;pod-manifest-path=/etc/kubernetes/manifests

3.  kube-proxy

    kube-proxyは、v1.17.1版では &#x2013;config, &#x2013;config-write-to、 &#x2013;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<a id="sec-1-2-9" name="sec-1-2-9"></a>

各サービスの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が動いているか確認<a id="sec-1-2-10" name="sec-1-2-10"></a>

    curl -s https://$MASTER_IP:6443/healthz

で、okが返ってくればとりあえずはOKです。

    curl -s https://$MASTER_IP:6443/api

でJSONが返ってくれば、kubectlは利用できる状態になっています。 `kubectl get nodes` とかをしたとき、ちゃんとノード一覧に自身が出てこない場合、どこかでエラーが出ているはずなので、いろいろなログを見ながら頑張る必要があります。

## 手動セットアップのつらさ<a id="sec-1-3" name="sec-1-3"></a>

冗長性とかを全部捨てて、最低限動作するまでを書いてみました。実際にはここから先で、Proxy/DNSの動作をするところまで確認していますが、それはまた後日ということで・・・。

手動でやってみてわかりましたが、Kubernetesは複数のミドルウェアをうまく組み合わせて構成しているという構成のため、それぞれの構成について冗長性を確保しなければならないと考えると、非常に辛いことは想像に容易いです。
これは確かにManagement serviceが欲しくなります。ただ、やはり手動で作成すると、ミドルウェアの構成であったり、ネットワークの構成であったりといったものは（いやでも）理解が進みます。

Hard wayであるにせよ、一回は利用する前・後で構いませんので、時間があるときに手動で作ってみるのはいかがでしょうか。

DNSの構成などについても、時間があれば記述します。