ビビリフクロウの足跡

いつもお世話になっているインターネットへの恩返し

Rancher 2.0で複数のKubernetes環境を管理してみる

2018年4月末にコンテナ管理プラットフォームであるRancherのバージョン2.0がGA(General Availability)となりました。対応するコンテナオーケストレーションツールをKubernetesに絞り、オンプレ/クラウド問わずKubernetesクラスタをマネジメントするOSSに昇華されているみたいです。今回はこのRancher 2.0を使って

  • 新しいKubernetesクラスタの作成
  • 既存のKubernetesクラスタのインポート
  • Helmでアプリケーションをデプロイ

というところまでをやってみたいと思います。

環境

今回は以下の環境で試用してみます。

  • OS
  • Rancher
    • Ver: v2.0.2
  • インポートするKubernetes
    • Ver: 1.10.0
  • RKE(Rancher Kubernetes Engine)のKubernetes
    • Ver: 1.10.1
  • 構成サーバ群
    • Rancherサーバ : rancher-server-01
    • RKE k8sマスター: rancher-k8s-master-01
    • RKE k8sノード#1: rancher-k8s-node-01
    • RKE k8sノード#2: rancher-k8s-node-02
    • 自前k8sマスター: tst-k8s-master01
    • 自前k8sノード#1: tst-k8s-node01
    • 自前k8sノード#2: tst-k8s-node02

事前準備

今回は検証なので、ポートは全開放でいきます。以下のコマンドを全ノードで叩きます。

firewall-cmd --set-default-zone=trusted

次に全ノードにDockerをインストールして、サービスを起動します。

yum -y install docker
systemctl enable docker
systemctl start docker

この時点でインポートする自前k8sを構築しておきます。注意点としては、kube-dnsIngress Controllerを忘れずにデプロイしておきます。

Rancherサーバの構築

Rancherサーバを構築していきますが、難しいことはありません。以下のコマンドを叩くだけです。

mkdir /var/lib/rancher
docker run -d --restart=unless-stopped -p 80:80 -p 443:443 -v /var/lib/rancher:/var/lib/rancher:Z rancher/rancher:v2.0.2

ここで/var/lib/rancherはRancherサーバの永続ボリュームとして使うディレクトリなので、お好みで調整してください。

またCentOSをはじめとしたRHEL系OSではSELinuxが有効になっているかと思いますので、-vオプションの末尾に:Zをつけて適切なラベリングを行うようにしておきます。

Rancherサーバが起動したら、ウェブブラウザからRancherサーバにログインします。するとまずadminユーザのパスワードを作成するよう求められるので、パスワードを入力して次に進みます。

f:id:bbrfkr:20180527133404p:plain

RancherサーバのURLはアクセスした際のURLがデフォルトで表示されていますので、そのままで次に進みます。

f:id:bbrfkr:20180527133617p:plain

すると以下のような画面が表示されます。クラスタを登録する前にHelm(k8sのパッケージマネージャ)を有効化しておきましょう。「Catalog」を選択します。

f:id:bbrfkr:20180527133935p:plain

「Helm Stable」のスイッチを「Enabled」にします。

f:id:bbrfkr:20180527134026p:plain

これでHelmの有効化は完了です。

新しいKubernetesクラスタの作成

次にクラスタを登録していきましょう。まずはRKEクラスタを作成・登録します。「Clusters」を選択し、「Add Cluster」をクリックします。

f:id:bbrfkr:20180527134301p:plain

すると以下のような画面になります。今回は一般的な仮想マシンからk8sクラスタを作成しますので、「CUSTOM」を選択し、「Next」をクリックします。

f:id:bbrfkr:20180527134558p:plain

次の画面ではRKE k8sクラスタを作成するのに必要なコマンドが表示されます。k8sマスターを登録をするには「Node Role」の

  • etcd
  • Control
  • Worker

にチェックを入れ、記載されているコマンドを実行します。が、SELinuxが有効な環境に対してはこのままではダメで、-vオプションの末尾に:Zをつけるのを忘れないようにしましょう。

f:id:bbrfkr:20180527135124p:plain

同様にk8sノードを登録するには「Node Role」の

  • Worker

にチェックを入れ、記載されているコマンドを実行します。-vオプションの末尾に:Zをつけるのを忘れずに。

それぞれのサーバでコマンド実行が終わったら「Done」をクリックします。そうするとRKE k8sクラスタの作成が始まります。

f:id:bbrfkr:20180527135420p:plain

既存のKubernetesクラスタのインポート

RKE k8sクラスタの作成を待っている間に既存k8sクラスタをRancherサーバにインポートしましょう。「Clusters」を選択して「Add Cluster」をクリックし、今度は「IMPORT」を選択します。その後「Next」をクリックしましょう。

f:id:bbrfkr:20180527135816p:plain

すると既存k8sクラスタをRancherサーバに登録するためのコマンドが発行されますので、k8sマスター上で二番目のコマンドを叩きます。叩いたら「Done」をクリックします。

f:id:bbrfkr:20180527140041p:plain

戻った画面で「State」の値が「Active」になっていればRKE k8sクラスタの作成・登録、既存k8sクラスタの登録は完了です。

f:id:bbrfkr:20180527140407p:plain

Helmでアプリケーションをデプロイ

Rancherに二つのk8sクラスタが登録されたので、それぞれのクラスタ上にアプリケーションをデプロイしてみましょう。まずはRKE k8sクラスタ上にデプロイしてみます。「Global」から「Cluster: rke-cluster-01 > Default」をクリックします。

f:id:bbrfkr:20180527140648p:plain

「Catalog Apps」を選択し、「Launch」をクリックします。

f:id:bbrfkr:20180527140943p:plain

表示されたカタログ群から「dokuwiki」を選択します。

f:id:bbrfkr:20180527141051p:plain

今回はPersistent Volumeをまだサポートしていないクラスタなので、アプリケーションのデプロイでPersistent Volumeを使わない設定にします。またデフォルトではLoadBalancerタイプのServiceが作られるので、これをClusterIPに変更します。下のほうに進んで「Add Answer」を2回クリックし、以下のKey-Valueを入力します。その後「Launch」をクリックします。

  • serviceType = ClusterIP
  • persistence.enabled = false

f:id:bbrfkr:20180527141526p:plain

しばらくすると以下のようにゲージが緑になります。この状態になれば必要なPodは上がっている状態です。

f:id:bbrfkr:20180527141716p:plain

では、Ingressを作成して外部からdokuwikiにアクセスできるようにしましょう。「Workloads」を選択し「Load Balancing」タブを選んでから「Add Ingress」をクリックします。

f:id:bbrfkr:20180527152045p:plain

Ingressを作成する際のパラメータは以下の通りにします。

  • Name: (任意の名前)
  • Namespace: (作成されたNamespace)
  • Rules
    • Automatically generate a .xip.io hostname
    • Target: (作成されたService)
    • Port: 80

f:id:bbrfkr:20180527152726p:plain

以上、入力したら下の「Save」をクリックします。少し経つと以下の通りURLが発行されるので、アクセスしてみましょう。dokuwikiのホームページが表示されます!

f:id:bbrfkr:20180527152913p:plain

f:id:bbrfkr:20180527152939p:plain

次は既存k8sクラスタ上にアプリケーションをデプロイしてみます。「Global」から「Cluster: my-cluster-01 > Default」をクリックします。

f:id:bbrfkr:20180527153218p:plain

「Catalog Apps」を選択し、「Launch」をクリックします。

f:id:bbrfkr:20180527153309p:plain

表示されたカタログ群から「zeppelin」を選択します。

f:id:bbrfkr:20180527153401p:plain

このカタログではデフォルトのCPU・メモリリソースを最低2000millicore、4096MiB確保するように設定されているので、今回構築したクラスタで展開できる値に修正します。「Add Answer」を2回クリックし、以下のKey-Valueを入力します。その後「Launch」をクリックします。

  • zeppelin.resources.limits.cpu: 1000m
  • zeppelin.resources.limits.meory: 2048Mi

f:id:bbrfkr:20180527153631p:plain

ゲージが緑になるまで待ちましょう。

f:id:bbrfkr:20180527153739p:plain

dokuwikiと同様、Ingressを作っていきます。「Workloads」を選択し「Load Balancing」タブを選んでから「Add Ingress」をクリックします。

f:id:bbrfkr:20180527153856p:plain

Ingressを作成する際のパラメータは以下の通りにします。

  • Name: (任意の名前)
  • Namespace: (作成されたNamespace)
  • Rules
    • Specify a hostname use
      • Request Host: (Ingress Controllerに対応した名前)
    • Target: (作成されたService)
    • Port: 8080

f:id:bbrfkr:20180527154002p:plain

以上、入力したら下の「Save」をクリックします。表示されたURLにアクセスしてみましょう。zeppelinのホームページが表示されます!

f:id:bbrfkr:20180527154255p:plain

f:id:bbrfkr:20180527154318p:plain

API Aggregationを有効化してPrometheusのメトリクスからHorizonPodAutoscalerでPodのオートスケールをする

やたら長いタイトルですが…

KubernetesではAPI Aggregationといって、Custom Metrics API Serverを用意しkube-apiserverと連携することでAPIを拡張することができます。例えば今回のタイトルにもある通り、Prometheusが取得したメトリクスをAPIから取得できるようにすることができます。この記事では以下を可能とするKubernetesの設定を説明します。

  • API Aggregationの有効化
  • Prometheus用のCustom Metrics API Serverの構築とkube-apiserverとの連携

API Aggregationの有効化

API Aggregationを有効化するにはkube-apiserverkube-controller-managerに設定が必要になります。

まず、kube-controller-managerに以下のオプションを設定して再起動します。

--horizontal-pod-autoscaler-use-rest-clients=true

次にkube-apiserverの認証方式において、x509証明書認証を有効化するために、CA証明書を用意します。CA証明書のファイルパスを/etc/kubernetes/certs/ca.crtとします。

さらにkube-apiserverとCustom Metrics API Serverとが連携するために使うクライアント証明書と秘密鍵、そしてクライアント証明書の署名に使ったCAのCA証明書を用意します。それぞれのファイルパスを/etc/kubernetes/certs/proxy-client.crt/etc/kubernetes/certs/proxy-client.key/etc/kubernetes/certs/proxy-ca.crtとします。

その後kube-apiserverに以下のオプションを設定します

--client-ca-file=/etc/kubernetes/certs/ca.crt
--requestheader-client-ca-file=/etc/kubernetes/certs/proxy-ca.crt
--requestheader-allowed-names=''
--requestheader-extra-headers-prefix=X-Remote-Extra-
--requestheader-group-headers=X-Remote-Group
--requestheader-username-headers=X-Remote-User
--proxy-client-cert-file=/etc/kubernetes/certs/proxy-client.crt
--proxy-client-key-file=/etc/kubernetes/certs/proxy-client.key

最後にkube-apiserverの再起動を行うのですが、もし既に--client-ca-fileの値を指定していて今回CA証明書を変更する場合、既にAPIサーバ間連携に使用するConfigMap extension-apiserver-authenticationができてしまっているので、これをあらかじめ削除しておきます。

kubectl -n kube-system delete configmap extension-apiserver-authentication

その後、kube-apiserverを再起動します。

これでAPI Aggregationの設定は完了です。

Prometheus用のCustom Metrics API Serverの構築とkube-apiserverとの連携

Prometheus用のCustom Metrics API Serverを構築し、kube-apiserverにAPIエンドポイントを作成してkube-apiserverとCustom Metrics API Serverとを連携させます。

前提条件として名前空間kube-systemにPrometheusは構築済みであるとします。ここではPrometheusサーバ(port: 9090)のホスト名をprometheus-serverであるとします。

まずはCustom Metrics API Serverを構築するためのKubernetes Manifestをダウンロードします。

git clone https://github.com/DirectXMan12/k8s-prometheus-adapter
cd k8s-prometheus-adapter

ダウンロードしてきたManifestの中にあるPrometheusサーバの指定を変更します。

sed -i 's/prometheus.prom.svc:9090/prometheus-server/g' deploy/manifests/custom-metrics-apiserver-deployment.yaml

また、Manifest内でリソースを作成する名前空間kube-systemに変更します。

sed -i 's/namespace: custom-metrics/namespace: kube-system/g' deploy/manifests/*

さらにPullしてくるCustom Metrics API Serverコンテナイメージのタグを付与します。

sed -i 's/directxman12\/k8s-prometheus-adapter/directxman12\/k8s-prometheus-adapter:advanced-config/g' deploy/manifests/custom-metrics-apiserver-deployment.yaml

次にCustom Metrics API Serverのサーバ証明書秘密鍵を作成します。ファイル名はそれぞれserving.crtserving.keyとします。

作成したサーバ証明書秘密鍵を使用してSecret cm-adapter-serving-certsを作成します。

kubectl -n kube-system create secret generic --from-file=serving.crt=serving.crt --from-file=serving.key=serving.key cm-adapter-serving-certs

最後にCustom Metrics API Serverを構築します。

kubectl create -f deploy/manifests/

構築がうまくいっていれば、APIを叩くと以下のような出力が得られると思います。

kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1" | jq .
{
  "kind": "APIResourceList",
  "apiVersion": "v1",
  "groupVersion": "custom.metrics.k8s.io/v1beta1",
  "resources": [
    {
      "name": "pods/fs_sector_writes",
      "singularName": "",
      "namespaced": true,
      "kind": "MetricValueList",
      "verbs": [
        "get"
      ]
    },
<中略>
    {
      "name": "jobs.batch/kube_pod_status_phase",
      "singularName": "",
      "namespaced": true,
      "kind": "MetricValueList",
      "verbs": [
        "get"
      ]
    }
  ]
}

PrometheusのメトリクスからPodをオートスケールする

では実際に構築したCustom Metrics API Serverを使ってオートスケールを実現してみましょう。 まずはテスト用にDeployment、Serviceを作ります。

kubectl create deploy httpd --image httpd
kubectl expose deploy httpd --port 80
kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
httpd        ClusterIP   172.16.132.62   <none>        80/TCP    10s
kubernetes   ClusterIP   172.16.0.1      <none>        443/TCP   56m

次にHorizontalPodAutoScalerをDeployment httpdに対して作ります。

cat << EOF > hpa-httpd.yaml
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
  name: httpd
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: httpd
  minReplicas: 1
  maxReplicas: 8
  metrics:
  - type: Pods
    pods:
      metricName: cpu_usage
      targetAverageValue: 1m
EOF
kubectl create -f hpa-httpd.yaml

HPAが作成出来たら、以下のスクリプトを実行してPodに負荷をかけます。

cat << EOF > hpa-test.sh
#!/bin/sh
count=1
while(true) do
  curl http://172.16.132.62 > /dev/null 2>&1 &
  echo access count = \$((count++))
done
EOF
chmod 755 hpa-test.sh
./hpa-test.sh

するとPodが増えていく様が見て取れるはずです。

KubernetesでHorizontal Pod Autoscalerを使えるようにする

KubernetesにおけるHorizontal Pod Autoscaler(HPA)とは、Podの過負荷時(CPU使用量増、メモリ使用量増など)においてPodをオートスケーリングするリソースです。DeploymentやReplicaSetに紐付けて使います。

HPAを使ってオートスケーリングを実現するには、Podの負荷を監視する必要があります。つまり種々のメトリクス(CPU使用率、メモリ使用率など)を取得する必要があります。

Kubernetesでもっとも簡単にメトリクス取得する方法はおそらくHeapsterという監視ソフトウェアを使うことです。Helmを使って簡単にインストールできます。

# helm install stable/heapster --namespace kube-system

Kubernetes v.1.10だと、HPAを実現するデフォルトのメトリクス取得先はAggregation APIになっており、Heapsterのメトリクスを見てくれません。そこでkube-controller-managerの起動オプションに次のオプションを加えます。

--horizontal-pod-autoscaler-use-rest-clients=false

これでHeapsterを使ってオートスケーリングを実現することができます。

Haproxy Ingress Controllerの導入

OpenShiftではおなじみのroute機能を、Kubernetes上でもIngressを使って再現してみたいと思います。

まずはDNSワイルドカードの有効化から。今回は*.ha-kubernetes.internalというアドレスをhaproxyポッドに割り当てます。

DNSサーバの設定

# vi /etc/dnsmasq.conf
address=/ha-kubernetes.internal/192.168.0.89
# systemctl restart dnsmasq

例のごとくマルチマスターKubernetes環境を前提とするので、フロントエンドのロードバランサ(これもhaproxy)に対し80や443ポートにアクセスが来たら、マスターの同一ポートにリクエストを流すように設定します。

フロントエンドロードバランサの設定(haproxy-*)

# vi /etc/haproxy/haproxy.cfg
...
frontend router_http *:80
    default_backend    router_http

frontend router_https *:443
    default_backend    router_https

frontend router_stat *:1936
    default_backend    router_stat
...
backend router_http
    balance   leastconn
    server    master1  kube-master-01.bbrfkr.mydns.jp:80  check
    server    master2  kube-master-02.bbrfkr.mydns.jp:80  check  backup
    server    master3  kube-master-03.bbrfkr.mydns.jp:80  check  backup

backend router_https
    balance   leastconn
    server    master1  kube-master-01.bbrfkr.mydns.jp:443  check
    server    master2  kube-master-02.bbrfkr.mydns.jp:443  check  backup
    server    master3  kube-master-03.bbrfkr.mydns.jp:443  check  backup

backend router_stat
    balance   leastconn
    server    master1  kube-master-01.bbrfkr.mydns.jp:1936  check
    server    master2  kube-master-02.bbrfkr.mydns.jp:1936  check  backup
    server    master3  kube-master-03.bbrfkr.mydns.jp:1936  check  backup
# systemctl restart haproxy

デフォルトでkube-apiserverはnodePortに30000-32767の間しか指定できないので、この縛りを緩くします。

nodeportレンジの変更(kube-master-*)

# sed -i 's/KUBE_API_ARGS="/KUBE_API_ARGS="--service-node-port-range 1-32767 /g' /etc/kubernetes/apiserver
# systemctl restart kube-apiserver

これからが実際のHaproxy Ingress Controllerの設定です。

haproxy ingress controllerの作成

  • デフォルトページ(404)作成
# vi ingress-default-deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    run: ingress-default-backend
  name: ingress-default-backend
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
      run: ingress-default-backend
  template:
    metadata:
      labels:
        run: ingress-default-backend
    spec:
      containers:
        - name: ingress-default-backend
          image: gcr.io/google_containers/defaultbackend:1.0
          ports:
          - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  labels:
    run: ingress-default-backend
  name: ingress-default-backend
  namespace: kube-system
spec:
  ports:
  - name: port-1
    port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    run: ingress-default-backend
# kubectl create -f ingress-default-deployment.yaml
  • controllerのコンフィグ作成
# vi haproxy-configmap.yaml
apiVersion: v1
data:
    dynamic-scaling: "true"
    backend-server-slots-increment: "4"
kind: ConfigMap
metadata:
  name: haproxy-configmap
  namespace: kube-system
# kubectl create -f haproxy-configmap.yaml
  • ingress controllerのデプロイ
# vi haproxy-ingress-deployment.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: haproxy-ingress
  namespace: kube-system

---

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: haproxy-ingress-clusterrole
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - endpoints
      - nodes
      - pods
      - secrets
    verbs:
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - nodes
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - services
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - "extensions"
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - ""
    resources:
        - events
    verbs:
        - create
        - patch
  - apiGroups:
      - "extensions"
    resources:
      - ingresses/status
    verbs:
      - update

---

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  name: haproxy-ingress-role
  namespace: kube-system
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - pods
      - secrets
      - namespaces
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - configmaps
    resourceNames:
      # Defaults to "<election-id>-<ingress-class>"
      # Here: "<ingress-controller-leader>-<haproxy>"
      # This has to be adapted if you change either parameter
      # when launching the haproxy-ingress-controller.
      - "ingress-controller-leader-haproxy"
    verbs:
      - get
      - update
  - apiGroups:
      - ""
    resources:
      - configmaps
    verbs:
      - create
  - apiGroups:
      - ""
    resources:
      - endpoints
    verbs:
      - get

---

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: haproxy-ingress-role-nisa-binding
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: haproxy-ingress-role
subjects:
  - kind: ServiceAccount
    name: haproxy-ingress
    namespace: kube-system

---

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: haproxy-ingress-clusterrole-nisa-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: haproxy-ingress-clusterrole
subjects:
  - kind: ServiceAccount
    name: haproxy-ingress
    namespace: kube-system

---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    run: haproxy-ingress
  name: haproxy-ingress
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
      run: haproxy-ingress
  template:
    metadata:
      labels:
        run: haproxy-ingress
    spec:
      containers:
      - name: haproxy-ingress
        image: quay.io/jcmoraisjr/haproxy-ingress
        args:
        - --default-backend-service=kube-system/ingress-default-backend
        - --default-ssl-certificate=kube-system/tls-secret
        - --configmap=$(POD_NAMESPACE)/haproxy-configmap
        - --reload-strategy=native
        ports:
        - name: http
          containerPort: 80
        - name: https
          containerPort: 443
        - name: stat
          containerPort: 1936
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
      serviceAccountName: haproxy-ingress 
# kubectl create -f haproxy-ingress-deployment.yaml
  • ingress controllerの外部公開サービス作成
# vi haproxy-ingress-svc.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    run: haproxy-ingress
  name: haproxy-ingress
  namespace: kube-system
spec:
  type:
    NodePort
  ports:
  - name: port-1
    port: 80
    protocol: TCP
    targetPort: 80
    nodePort: 80
  - name: port-2
    port: 443
    protocol: TCP
    targetPort: 443
    nodePort: 443
  - name: port-3
    port: 1936
    protocol: TCP
    targetPort: 1936
    nodePort: 1936
  selector:
    run: haproxy-ingress
# kubectl create -f haproxy-ingress-svc.yaml

ここまでで、Haproxy Ingress Controllerの構成はおしまいです。実際に動作確認をしてみましょう。

動作確認

  • エンドポイントサービスの作成
# vi httpd-deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    run: httpd
  name: httpd
spec:
  replicas: 1
  selector:
    matchLabels:
      run: httpd
  template:
    metadata:
      labels:
        run: httpd
    spec:
      containers:
        - name: httpd
          image: httpd
          ports:
          - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  labels:
    run: httpd
  name: httpd
  namespace: default
spec:
  ports:
    - name: port-1
      port: 80
      protocol: TCP
      targetPort: 80
  selector:
    run: httpd
# kubectl create -f httpd-deployment.yaml
# vi httpd-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: httpd
spec:
  rules:
  - host: test.ha-kubernetes.internal
    http:
      paths:
      - path: /
        backend:
          serviceName: httpd
          servicePort: 80
# kubectl create -f httpd-ingress.yaml
  • ブラウザでtest.ha-kubernetes.internalにアクセス

Kubernetes環境にDashboardとDNSサービスをデプロイする

前回作成したマルチマスターKubernetes環境はKubernetesの最小構成なので、今回はDashboardDNSサービスをデプロイしたいと思います。 まずはDashboardのデプロイから。

Dashboardの設定

  • kube-controller-managerの設定で、ServiceAccountがAPIサーバとの通信に使うルートCA証明書を持つように設定する
# sed -i 's@KUBE_CONTROLLER_MANAGER_ARGS="@KUBE_CONTROLLER_MANAGER_ARGS="--root-ca-file=/etc/kubernetes/certs/apiserver.crt @g' /etc/kubernetes/controller-manager
# systemctl restart kube-controller-manager
  • 既存のServiceAccount「default」を削除(自動的に新しいCA証明書を持つServiceAccountが再作成されます)
# kubectl delete sa default
# kubectl delete sa default -n kube-system
# kubectl delete sa default -n kube-public
  • APIサーバ側でsystem:anonymousがNamespace"kube-system"のResource"*"にRW権限を付ける
    • ABACの設定
# vi /etc/kubernetes/abac-policy.json
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"system:anonymous", "namespace": "kube-system", "resource": "*"}}
  • APIサーバの認可機能で、RBACも適用するように設定する。
# sed -i 's/--authorization-mode=ABAC/--authorization-mode=RBAC,ABAC/g' /etc/kubernetes/apiserver
# systemctl restart kube-apiserver
  • manifestを落としてくる
# curl -O https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml
  • manifestの修正
    • Dashboard自体のベーシック認証を有効化する
      • オプション指定
...
    spec:
      containers:
      - name: kubernetes-dashboard
        image: k8s.gcr.io/kubernetes-dashboard-amd64:v1.8.3
        ports:
        - containerPort: 8443
          protocol: TCP
        args:
          - --auto-generate-certificates
          - --authentication-mode     # ここを追加
          - basic                     # ここを追加
          # Uncomment the following line to manually specify Kubernetes API server Host
          # If not specified, Dashboard will attempt to auto discover the API server and connect
          # to it. Uncomment only if the default does not work.
          # - --apiserver-host=http://my-address:port
...
  • kubernetes-dashboardポッドのデプロイ
# kubectl create -f kubernetes-dashboard.yaml

次にDNSサービスのデプロイですねー

kube-dns設定

  • kubeletにkube-dnsサービスのIPと管理ドメインを設定する(kube-*)
# sed -i 's/KUBELET_ARGS="/KUBELET_ARGS="--cluster-dns=172.16.0.2 --cluster-domain=cluster.local /g' /etc/kubernetes/kubelet
# systemctl restart kubelet
  • manifestをダウンロード
# curl -O https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dns/kube-dns.yaml.sed
# mv kube-dns.yaml.sed kube-dns.yaml
  • manifestを修正して、以下を設定
# sed -i "s/\$DNS_SERVER_IP/172.16.0.2/g" kube-dns.yaml
# sed -i "s/\$DNS_DOMAIN/cluster.local/g" kube-dns.yaml
  • kube-dnsポッドのデプロイ
# kubectl create -f kube-dns.yaml

これでDashboardDNSサービス用のPodなりServiceなりが正常に上がるはずです。

Dashboardはこんな感じ。

f:id:bbrfkr:20180325202055p:plain

DNSサービスの稼働確認をするために、httpdポッドを二つ、別のノードに上げてみましょう。Masterノードで以下を叩きます。

# kubectl create deploy --image httpd httpd-1
# kubectl create deploy --image httpd httpd-2
# kubectl expose deploy httpd-1 --port 80
# kubectl expose deploy httpd-2 --port 80

ポッドが上がったら、以下のコマンドでcurlをそれぞれのポッドにインストールします。

# kubectl exec <httpd-1のポッド> -- apt-get update 
# kubectl exec <httpd-1のポッド> -- apt-get install -y curl
# kubectl exec <httpd-2のポッド> -- apt-get update
# kubectl exec <httpd-2のポッド> -- apt-get install -y curl

curlをインストールしたら、互いのサービスに対して名前解決ができるかを確かめます。

# kubectl exec <httpd-1のポッド> -- curl httpd-2
# kubectl exec <httpd-2のポッド> -- curl httpd-1

複数マスターを持つ高可用性Kubernetesクラスタを作る

先日、keepalivedを用いてhaproxyを冗長化する記事を上げましたが、この高可用性ロードバランサを用いて複数マスターを持つ高可用性Kubernetestクラスタを作ってみたので、その手順を公開いたします。

環境

アーキテクチャはシンプルで、kubeletやkubectlのAPIリクエストを直接マスターに送るのではなく、間にhaproxyを挟むことによってマスターの冗長化を実現しています。

f:id:bbrfkr:20180318233148p:plain

なお、利用OSはCentOS 7.4になります。

手順

以降、実際の手順です。

共通設定(haproxy-01, haproxy-02, kube-master-01, kube-master-02, kube-master-03, kube-node-01, kube-node-02)

  • hosts設定
# vi /etc/hosts
192.168.0.82  kube-master-01.bbrfkr.mydns.jp
192.168.0.83  kube-master-02.bbrfkr.mydns.jp
192.168.0.84  kube-master-03.bbrfkr.mydns.jp
192.168.0.85  kube-node-01.bbrfkr.mydns.jp
192.168.0.86  kube-node-02.bbrfkr.mydns.jp
192.168.0.87  haproxy-01.bbrfkr.mydns.jp
192.168.0.88  haproxy-02.bbrfkr.mydns.jp

検証のため、無効化します。

# sed -i 's/SELINUX=.*/SELINUX=permissive/g' /etc/selinux/config
# systemctl disable firewalld
# reboot

LBの構築(haproxy-*)

  • haproxy、keepalivedのインストール(haproxy-01, haproxy-02)
# yum -y install haproxy keepalived
  • haproxyの設定(haproxy-01, haproxy-02)
# vi /etc/haproxy/haproxy.cfg
frontend api *:6443
    default_backend    api

frontend etcd *:2379
    default_backend    etcd

backend api
    balance   leastconn
    server    master1  kube-master-01.bbrfkr.mydns.jp:6443  check
    server    master2  kube-master-02.bbrfkr.mydns.jp:6443  check  backup
    server    master3  kube-master-03.bbrfkr.mydns.jp:6443  check  backup

backend etcd
    balance   leastconn
    server    master1  kube-master-01.bbrfkr.mydns.jp:2379  check
    server    master2  kube-master-02.bbrfkr.mydns.jp:2379  check  backup
    server    master3  kube-master-03.bbrfkr.mydns.jp:2379  check  backup
  • haproxyの起動と自動起動設定(haproxy-01, haproxy-02)
# systemctl enable haproxy
# systemctl start haproxy
# vi /etc/keepalived/keepalived.conf
vrrp_script chk_haproxy {
    script "systemctl is-active haproxy"
}

vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 1
    priority 101
    virtual_ipaddress {
        192.168.0.89
    }
    track_script {
        chk_haproxy
    }
}
# vi /etc/keepalived/keepalived.conf
vrrp_script chk_haproxy {
    script "systemctl is-active haproxy"
}

vrrp_instance VI_1 {
    state BACKUP
    interface eth0
    virtual_router_id 1
    priority 100
    virtual_ipaddress {
        192.168.0.89
    }
    track_script {
        chk_haproxy
    }
}
# systemctl enable keepalived
# systemctl start keepalived

Dockerのインストール(kube-*)

  • パッケージのインストール
# yum -y install docker
  • コンテナボリューム用ディスクのセットアップ
# cat <<EOF > /etc/sysconfig/docker-storage-setup
DEVS=/dev/vda
VG=docker-vg
EOF
# docker-storage-setup
  • ヘアピンNATの有効化(これをしないと、Podが自分自身のServiceにアクセスできなくなる...)
# sed -i "s/OPTIONS='/OPTIONS='--userland-proxy=false /g" /etc/sysconfig/docker
# systemctl enable docker
# systemctl start docker

etcdのインストール(kube-master-*)

  • パッケージのインストール(kube-master-*)
# yum -y install etcd
  • 設定ファイルの書き換え(kube-master-01)
# vi /etc/etcd/etcd.conf
#[Member]
ETCD_NAME=kube-master-01
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_PEER_URLS="http://192.168.0.82:2380"
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"

#[Clustering]
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.0.82:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.0.82:2379"
ETCD_INITIAL_CLUSTER="kube-master-01=http://192.168.0.82:2380,kube-master-02=http://192.168.0.83:2380,kube-master-03=http://192.168.0.84:2380"
ETCD_INITIAL_CLUSTER_STATE="new"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster-for-kubernetes"
  • 設定ファイルの書き換え(kube-master-02)
# vi /etc/etcd/etcd.conf
#[Member]
ETCD_NAME=kube-master-02
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_PEER_URLS="http://192.168.0.83:2380"
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"

#[Clustering]
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.0.83:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.0.83:2379"
ETCD_INITIAL_CLUSTER="kube-master-01=http://192.168.0.82:2380,kube-master-02=http://192.168.0.83:2380,kube-master-03=http://192.168.0.84:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster-for-kubernetes"
  • 設定ファイルの書き換え(kube-master-03)
# vi /etc/etcd/etcd.conf
#[Member]
ETCD_NAME=kube-master-03
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_PEER_URLS="http://192.168.0.84:2380"
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"

#[Clustering]
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.0.84:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.0.84:2379"
ETCD_INITIAL_CLUSTER="kube-master-01=http://192.168.0.82:2380,kube-master-02=http://192.168.0.83:2380,kube-master-03=http://192.168.0.84:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster-for-kubernetes"
  • サービスの起動と自動起動設定(kube-master-02, kube-master-03)
# systemctl enable etcd
# systemctl start etcd
  • サービスの起動と自動起動設定(kube-master-01)
# systemctl enable etcd
# systemctl start etcd
# etcdctl member list
# etcdctl cluster-health
  • コンテナ用ネットワーク設定作成(kube-master-01)
# etcdctl mkdir /ha-kubernetes/network
# etcdctl mk /ha-kubernetes/network/config '{ "Network": "10.1.0.0/16", "SubnetLen": 24, "Backend": { "Type": "vxlan" } }'

flannelのインストール - master編 - (kube-master-*)

  • wgetのインストール
# yum -y install wget
  • バイナリのダウンロード・展開と配置
# mkdir flanneld && cd flanneld
# wget https://github.com/coreos/flannel/releases/download/v0.10.0/flannel-v0.10.0-linux-amd64.tar.gz
# tar xvzf flannel-v0.10.0-linux-amd64.tar.gz
# mv flanneld mk-docker-opts.sh /usr/bin
  • ごみ掃除
# cd ..
# rm -rf flanneld
  • systemdファイル作成
# vi /usr/lib/systemd/system/flanneld.service
[Unit]
Description=Flanneld overlay address etcd agent
After=network.target
After=network-online.target
Wants=network-online.target
After=etcd.service
Before=docker.service

[Service]
Type=notify
EnvironmentFile=/etc/sysconfig/flanneld
EnvironmentFile=-/etc/sysconfig/docker-network
ExecStart=/usr/bin/flanneld-start $FLANNEL_OPTIONS
ExecStartPost=/usr/bin/mk-docker-opts.sh -k DOCKER_NETWORK_OPTIONS -d /run/flannel/docker
Restart=on-failure

[Install]
WantedBy=multi-user.target
RequiredBy=docker.service
# touch /usr/bin/flanneld-start
# chown root:root /usr/bin/flanneld-start
# chmod 755 /usr/bin/flanneld-start
# vi /usr/bin/flanneld-start
#!/bin/sh

exec /usr/bin/flanneld \
        -etcd-endpoints=${FLANNEL_ETCD_ENDPOINTS:-${FLANNEL_ETCD}} \
        -etcd-prefix=${FLANNEL_ETCD_PREFIX:-${FLANNEL_ETCD_KEY}} \
        "$@"
# mkdir /usr/lib/systemd/system/docker.service.d
# vi /usr/lib/systemd/system/docker.service.d/flannel.conf
[Service]
EnvironmentFile=-/run/flannel/docker
  • 設定ファイルの編集
# vi /etc/sysconfig/flanneld
FLANNEL_ETCD_ENDPOINTS="http://127.0.0.1:2379"
FLANNEL_ETCD_PREFIX="/ha-kubernetes/network"
#FLANNEL_OPTIONS=""
# systemctl enable flanneld
# systemctl start flanneld
  • dockerサービス再起動
# systemctl restart docker

flannelのインストール - node編 - (kube-node-*)

  • wgetのインストール
# yum -y install wget
  • バイナリのダウンロード・展開と配置
# mkdir flanneld && cd flanneld
# wget https://github.com/coreos/flannel/releases/download/v0.10.0/flannel-v0.10.0-linux-amd64.tar.gz
# tar xvzf flannel-v0.10.0-linux-amd64.tar.gz
# mv flanneld mk-docker-opts.sh /usr/bin
  • ごみ掃除
# cd ..
# rm -rf flanneld
  • systemdファイル作成
# vi /usr/lib/systemd/system/flanneld.service
[Unit]
Description=Flanneld overlay address etcd agent
After=network.target
After=network-online.target
Wants=network-online.target
After=etcd.service
Before=docker.service

[Service]
Type=notify
EnvironmentFile=/etc/sysconfig/flanneld
EnvironmentFile=-/etc/sysconfig/docker-network
ExecStart=/usr/bin/flanneld-start $FLANNEL_OPTIONS
ExecStartPost=/usr/bin/mk-docker-opts.sh -k DOCKER_NETWORK_OPTIONS -d /run/flannel/docker
Restart=on-failure

[Install]
WantedBy=multi-user.target
RequiredBy=docker.service
# touch /usr/bin/flanneld-start
# chown root:root /usr/bin/flanneld-start
# chmod 755 /usr/bin/flanneld-start
# vi /usr/bin/flanneld-start
#!/bin/sh

exec /usr/bin/flanneld \
        -etcd-endpoints=${FLANNEL_ETCD_ENDPOINTS:-${FLANNEL_ETCD}} \
        -etcd-prefix=${FLANNEL_ETCD_PREFIX:-${FLANNEL_ETCD_KEY}} \
        "$@"
# mkdir /usr/lib/systemd/system/docker.service.d
# vi /usr/lib/systemd/system/docker.service.d/flannel.conf
[Service]
EnvironmentFile=-/run/flannel/docker
  • 設定ファイルの編集
# vi /etc/sysconfig/flanneld
FLANNEL_ETCD_ENDPOINTS="http://192.168.0.89:2379"
FLANNEL_ETCD_PREFIX="/ha-kubernetes/network"
#FLANNEL_OPTIONS=""
# systemctl enable flanneld
# systemctl start flanneld
  • dockerサービス再起動
# systemctl restart docker

kubernetesの設定 - master編 - (kube-master-*)

  • バイナリのダウンロード
# curl -O https://storage.googleapis.com/kubernetes-release/release/v1.9.1/kubernetes-server-linux-amd64.tar.gz
  • tarボールの展開
# tar xvzf kubernetes-server-linux-amd64.tar.gz
  • 不要物の削除
# rm -f kubernetes/server/bin/*.*
  • バイナリの移動
# mv kubernetes/server/bin/* /usr/bin/
  • ゴミ掃除
# rm -rf kubernetes*
  • kubeユーザの作成
# groupadd --system kube
# useradd \
      --home-dir "/home/kube" \
      --system \
      --shell /bin/false \
      -g kube \
      kube
# mkdir -p /home/kube
# chown kube:kube /home/kube
# mkdir -p /etc/kubernetes
# chown kube:kube /etc/kubernetes
  • master用systemdファイル配置
# vi /usr/lib/systemd/system/kube-apiserver.service
[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
After=network.target
After=etcd.service

[Service]
EnvironmentFile=-/etc/kubernetes/config
EnvironmentFile=-/etc/kubernetes/apiserver
User=kube
ExecStart=/usr/bin/kube-apiserver \
            $KUBE_LOGTOSTDERR \
            $KUBE_LOG_LEVEL \
            $KUBE_ETCD_SERVERS \
            $KUBE_API_PORT \
            $KUBE_API_ADDRESS \
            $KUBE_ALLOW_PRIV \
            $KUBE_SERVICE_ADDRESSES \
            $KUBE_ADMISSION_CONTROL \
            $KUBE_API_ARGS
Restart=on-failure
Type=notify
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
# vi /usr/lib/systemd/system/kube-controller-manager.service
[Unit]
Description=Kubernetes Controller Manager
Documentation=https://github.com/GoogleCloudPlatform/kubernetes

[Service]
EnvironmentFile=-/etc/kubernetes/config
EnvironmentFile=-/etc/kubernetes/controller-manager
User=kube
ExecStart=/usr/bin/kube-controller-manager \
            $KUBE_LOGTOSTDERR \
            $KUBE_LOG_LEVEL \
            $KUBE_CONTROLLER_MANAGER_ARGS
Restart=on-failure
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
# vi /usr/lib/systemd/system/kube-scheduler.service
[Unit]
Description=Kubernetes Scheduler Plugin
Documentation=https://github.com/GoogleCloudPlatform/kubernetes

[Service]
EnvironmentFile=-/etc/kubernetes/config
EnvironmentFile=-/etc/kubernetes/scheduler
User=kube
ExecStart=/usr/bin/kube-scheduler \
            $KUBE_LOGTOSTDERR \
            $KUBE_LOG_LEVEL \
            $KUBE_SCHEDULER_ARGS
Restart=on-failure
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
  • master用設定ファイル配置
# vi /etc/kubernetes/config
###
# kubernetes system config
#
# The following values are used to configure various aspects of all
# kubernetes services, including
#
#   kube-apiserver.service
#   kube-controller-manager.service
#   kube-scheduler.service
#   kubelet.service
#   kube-proxy.service
# logging to stderr means we get it in the systemd journal
KUBE_LOGTOSTDERR="--logtostderr=true"

# journal message level, 0 is debug
KUBE_LOG_LEVEL="--v=0"

# Should this cluster be allowed to run privileged docker containers
KUBE_ALLOW_PRIV="--allow-privileged=true"
# vi /etc/kubernetes/apiserver
###
# kubernetes system config
#
# The following values are used to configure the kube-apiserver
#

# The address on the local server to listen to.
KUBE_API_ADDRESS="--insecure-bind-address=127.0.0.1 --bind-address=0.0.0.0"

# The port on the local server to listen on.
KUBE_API_PORT="--insecure-port=8080 --secure-port=6443"

# Comma separated list of nodes in the etcd cluster
KUBE_ETCD_SERVERS="--etcd-servers=http://127.0.0.1:2379"

# Address range to use for services
KUBE_SERVICE_ADDRESSES="--service-cluster-ip-range=172.16.0.0/16"

# default admission control policies
KUBE_ADMISSION_CONTROL="--admission-control=NamespaceLifecycle,NamespaceExists,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota"

# Add your own!
KUBE_API_ARGS="--apiserver-count 3 --cert-dir /etc/kubernetes/certs --basic-auth-file=/etc/kubernetes/basic-auth-list.csv --authorization-mode=ABAC --authorization-policy-file=/etc/kubernetes/abac-policy.json"
# vi /etc/kubernetes/controller-manager
###
# The following values are used to configure the kubernetes controller-manager

# defaults from config and apiserver should be adequate

# Add your own!
KUBE_CONTROLLER_MANAGER_ARGS="--kubeconfig=/etc/kubernetes/kubeconfig --service-account-private-key-file=/etc/kubernetes/certs/apiserver.key"
# vi /etc/kubernetes/scheduler
###
# kubernetes scheduler config

# default config should be adequate

# Add your own!
KUBE_SCHEDULER_ARGS="--kubeconfig=/etc/kubernetes/kubeconfig"
# vi /etc/kubernetes/kubeconfig
apiVersion: v1
kind: Config
clusters:
  - cluster:
      server: https://127.0.0.1:6443
      insecure-skip-tls-verify: true
    name: ha-kubernetes
contexts:
  - context:
      cluster: ha-kubernetes
      user: kube-process
    name: kube-process-to-ha-kubernetes
current-context: kube-process-to-ha-kubernetes
users:
  - name: kube-process
    user:
      username: kube-process
      password: password
# vi /etc/kubernetes/abac-policy.json
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"group":"system:authenticated",  "nonResourcePath": "*", "readonly": true}}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"group":"system:unauthenticated", "nonResourcePath": "*", "readonly": true}}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"admin",     "namespace": "*",              "resource": "*",         "apiGroup": "*"                   }}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"kube-process",     "namespace": "*",              "resource": "*",         "apiGroup": "*"                   }}
# vi /etc/kubernetes/basic-auth-list.csv
password,admin,1
password,kube-process,2
  • 設定ファイル権限変更
# chown kube:kube /etc/kubernetes/*
  • パスワードファイルセキュリティ対策
# chmod 600 /etc/kubernetes/basic-auth-list.csv
  • OpenSSLの設定ファイル「openssl.cnf」のコピー(kube-master-01のみ)
# cd
# cp /etc/pki/tls/openssl.cnf .
  • openssl.cnfにSANの設定を入れる(kube-master-01のみ)
# sed -i 's/\[ v3_ca \]/\[ v3_ca \]\nsubjectAltName = @alt_names/g' openssl.cnf
# cat <<EOF >> openssl.cnf
[ alt_names ]
DNS.1 = kubernetes.default.svc
DNS.2 = kubernetes.default
DNS.3 = kubernetes
DNS.4 = localhost
IP.1 = 192.168.0.89
IP.2 = 172.16.0.1
EOF
  • APIサーバ用鍵の作成(kube-master-01のみ)
# openssl genrsa 2048 > apiserver.key
  • CSRの作成(kube-master-01のみ)
# openssl req -new -key apiserver.key -out apiserver.csr
# openssl x509 -days 7305 -req -signkey apiserver.key -extensions v3_ca -extfile ./openssl.cnf -in apiserver.csr -out apiserver.crt
# cp apiserver* /etc/kubernetes/certs
# scp apiserver* 192.168.0.83:/etc/kubernetes/certs
# scp apiserver* 192.168.0.84:/etc/kubernetes/certs
# chmod 600 /etc/kubernetes/certs/apiserver.key
# chown kube:kube /etc/kubernetes/certs/apiserver*
  • ごみ掃除(kube-master-01のみ)
# rm -f apiserver.*
# rm -f openssl.cnf
# systemctl enable kube-apiserver kube-scheduler kube-controller-manager
# systemctl start kube-apiserver
# systemctl start kube-scheduler kube-controller-manager

kubernetesの設定 - node編 1 - (kube-master-*)

  • swapの無効化
# vi /etc/fstab
# reboot
# mkdir -p /var/lib/kubelet
# chown root:root /var/lib/kubelet
  • node用systemdファイル配置
# vi /usr/lib/systemd/system/kubelet.service
[Unit]
Description=Kubernetes Kubelet Server
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
After=docker.service
Requires=docker.service

[Service]
WorkingDirectory=/var/lib/kubelet
EnvironmentFile=-/etc/kubernetes/config
EnvironmentFile=-/etc/kubernetes/kubelet
ExecStart=/usr/bin/kubelet \
            $KUBE_LOGTOSTDERR \
            $KUBE_LOG_LEVEL \
            $KUBELET_ADDRESS \
            $KUBELET_PORT \
            $KUBELET_HOSTNAME \
            $KUBE_ALLOW_PRIV \
            $KUBELET_POD_INFRA_CONTAINER \
            $KUBELET_ARGS
Restart=on-failure

[Install]
WantedBy=multi-user.target
# vi /usr/lib/systemd/system/kube-proxy.service
[Unit]
Description=Kubernetes Kube-Proxy Server
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
After=network.target

[Service]
EnvironmentFile=-/etc/kubernetes/config
EnvironmentFile=-/etc/kubernetes/proxy
ExecStart=/usr/bin/kube-proxy \
            $KUBE_LOGTOSTDERR \
            $KUBE_LOG_LEVEL \
            $KUBE_PROXY_ARGS
Restart=on-failure
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
  • node用設定ファイル配置
# vi /etc/kubernetes/kubelet
###
# kubernetes kubelet (minion) config

# The address for the info server to serve on (set to 0.0.0.0 or "" for all interfaces)
KUBELET_ADDRESS="--address=0.0.0.0"

# The port for the info server to serve on
KUBELET_PORT="--port=10250"

# You may leave this blank to use the actual hostname
# KUBELET_HOSTNAME="--hostname-override=127.0.0.1"
KUBELET_HOSTNAME=""

# pod infrastructure container
KUBELET_POD_INFRA_CONTAINER="--pod-infra-container-image=gcr.io/google_containers/pause-amd64:3.0"

# Add your own!
KUBELET_ARGS="--kubeconfig=/etc/kubernetes/kubeconfig --cgroup-driver=systemd --runtime-cgroups=/systemd/system.slice --kubelet-cgroups=/systemd/system.slice"
# vi /etc/kubernetes/proxy
###
# kubernetes proxy config

# default config should be adequate

# Add your own!
KUBE_PROXY_ARGS="--kubeconfig=/etc/kubernetes/kubeconfig --cluster-cidr=172.16.0.0/16"
  • 設定ファイル権限変更
# chown kube:kube /etc/kubernetes/*
# systemctl enable kubelet kube-proxy
# systemctl start kubelet kube-proxy

kubernetesの設定 - node編 2 - (kube-node-*)

  • バイナリのダウンロード
# curl -O https://storage.googleapis.com/kubernetes-release/release/v1.9.1/kubernetes-node-linux-amd64.tar.gz
  • tarボールの展開
# tar xvzf kubernetes-node-linux-amd64.tar.gz
  • 不要物の削除
# rm -f kubernetes/node/bin/*.*
  • バイナリの移動
# mv kubernetes/node/bin/* /usr/bin/
  • ゴミ掃除
# rm -rf kubernetes*
  • swapの無効化(node)
# vi /etc/fstab
# reboot
  • kubeユーザの作成
# groupadd --system kube
# useradd --home-dir "/home/kube" \
      --system \
      --shell /bin/false \
      -g kube \
      kube
# mkdir -p /home/kube
# chown kube:kube /home/kube
# mkdir -p /var/lib/kubelet
# chown root:root /var/lib/kubelet
# mkdir -p /etc/kubernetes
# chown kube:kube /etc/kubernetes
  • node用systemdファイル配置
# vi /lib/systemd/system/kubelet.service
[Unit]
Description=Kubernetes Kubelet Server
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
After=docker.service
Requires=docker.service

[Service]
WorkingDirectory=/var/lib/kubelet
EnvironmentFile=-/etc/kubernetes/config
EnvironmentFile=-/etc/kubernetes/kubelet
ExecStart=/usr/bin/kubelet \
            $KUBE_LOGTOSTDERR \
            $KUBE_LOG_LEVEL \
            $KUBELET_ADDRESS \
            $KUBELET_PORT \
            $KUBELET_HOSTNAME \
            $KUBE_ALLOW_PRIV \
            $KUBELET_POD_INFRA_CONTAINER \
            $KUBELET_ARGS
Restart=on-failure

[Install]
WantedBy=multi-user.target
# vi /lib/systemd/system/kube-proxy.service
[Unit]
Description=Kubernetes Kube-Proxy Server
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
After=network.target

[Service]
EnvironmentFile=-/etc/kubernetes/config
EnvironmentFile=-/etc/kubernetes/proxy
ExecStart=/usr/bin/kube-proxy \
            $KUBE_LOGTOSTDERR \
            $KUBE_LOG_LEVEL \
            $KUBE_PROXY_ARGS
Restart=on-failure
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
  • node用設定ファイル配置
# vi /etc/kubernetes/config
###
# kubernetes system config
#
# The following values are used to configure various aspects of all
# kubernetes services, including
#
#   kube-apiserver.service
#   kube-controller-manager.service
#   kube-scheduler.service
#   kubelet.service
#   kube-proxy.service
# logging to stderr means we get it in the systemd journal
KUBE_LOGTOSTDERR="--logtostderr=true"

# journal message level, 0 is debug
KUBE_LOG_LEVEL="--v=0"

# Should this cluster be allowed to run privileged docker containers
KUBE_ALLOW_PRIV="--allow-privileged=true"
# vi /etc/kubernetes/kubelet
###
# kubernetes kubelet (minion) config

# The address for the info server to serve on (set to 0.0.0.0 or "" for all interfaces)
KUBELET_ADDRESS="--address=0.0.0.0"

# The port for the info server to serve on
KUBELET_PORT="--port=10250"

# You may leave this blank to use the actual hostname
# KUBELET_HOSTNAME="--hostname-override=127.0.0.1"
KUBELET_HOSTNAME=""

# pod infrastructure container
KUBELET_POD_INFRA_CONTAINER="--pod-infra-container-image=gcr.io/google_containers/pause-amd64:3.0"

# Add your own!
KUBELET_ARGS="--kubeconfig=/etc/kubernetes/kubeconfig --cgroup-driver=systemd --runtime-cgroups=/systemd/system.slice --kubelet-cgroups=/systemd/system.slice"
# vi /etc/kubernetes/proxy
###
# kubernetes proxy config

# default config should be adequate

# Add your own!
KUBE_PROXY_ARGS="--kubeconfig=/etc/kubernetes/kubeconfig --cluster-cidr=172.16.0.0/16"
# vi /etc/kubernetes/kubeconfig
apiVersion: v1
kind: Config
clusters:
  - cluster:
      server: https://192.168.0.89:6443
      insecure-skip-tls-verify: true
    name: ha-kubernetes
contexts:
  - context:
      cluster: ha-kubernetes
      user: kube-process
    name: kube-process-to-ha-kubernetes
current-context: kube-process-to-ha-kubernetes
users:
  - name: kube-process
    user:
      username: kube-process
      password: password
  • 設定ファイル権限変更
# chown kube:kube /etc/kubernetes/*
# systemctl enable kubelet kube-proxy
# systemctl start kubelet kube-proxy

haproxyをkeepalivedで冗長化して高可用性ロードバランサを構築する

ロードバランサとして活用できるhaproxyですが、ロードバランサ冗長性確保用デーモンであるkeepalivedを使って冗長化してみたので、その手法をメモしておきます。

なんでいきなりhaproxy + keepalivedかというと、Kubernetesの冗長化においてkubeletやkubectlからのkube-apiserverやetcdへのアクセスはMasterノードが複数になるためロードバランスしてやらなくてなりません。ですのでロードバランサが必須になるんですが、単純にロードバランサを追加するだけだとロードバランサ自体がSPOFになってしまうので、ロードバランサ自体の高可用性も勉強してみた、という形です。

構成

構成としては以下の通りとします。

  • Webサーバ
    • IP: 192.168.0.60, ホスト名: apache-01.bbrfkr.mydns.jp
    • IP: 192.168.0.61, ホスト名: apache-02.bbrfkr.mydns.jp
  • ロードバランサ
    • IP: 192.168.0.62, ホスト名: haproxy-01.bbrfkr.mydns.jp
    • IP: 192.168.0.63, ホスト名: haproxy-01.bbrfkr.mydns.jp
    • VIP: 192.168.0.64

VIP: 192.168.0.64へのHTTPアクセスを背後のhaproxy-01, haproxy-02が受け付け、haproxyはさらに背後のapache-01, apache-02にリクエストを流す、という単純な構成です。

構築手順

  • SELinux、firewalldの無効化(haproxy-01, haproxy-02)

検証のため、無効化します。

# sed -i 's/SELINUX=.*/SELINUX=permissive/g' /etc/selinux/config
# systemctl disable firewalld
# reboot
  • haproxy、keepalivedのインストール(haproxy-01, haproxy-02)
# yum -y install haproxy keepalived
  • haproxyの設定(haproxy-01, haproxy-02)
# vi /etc/haproxy/haproxy.cfg
frontend main *:80
    default_backend    web

backend web
    balance    roundrobin
    server    web1  192.168.0.60:80  check
    server    web1  192.168.0.61:80  check
  • haproxyの起動と自動起動設定(haproxy-01, haproxy-02)
# systemctl enable haproxy
# systemctl start haproxy
# vi /etc/keepalived/keepalived.conf
vrrp_script chk_haproxy {
    script "systemctl is-active haproxy"
}

vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 1
    priority 101
    virtual_ipaddress {
        192.168.0.64
    }
    track_script {
        chk_haproxy
    }
}
# vi /etc/keepalived/keepalived.conf
vrrp_script chk_haproxy {
    script "systemctl is-active haproxy"
}

vrrp_instance VI_1 {
    state BACKUP
    interface eth0
    virtual_router_id 1
    priority 100
    virtual_ipaddress {
        192.168.0.64
    }
    track_script {
        chk_haproxy
    }
}
# systemctl enable keepalived
# systemctl start keepalived

動作確認

構築手順で実施した作業で、haproxy-01にVIP: 192.168.0.64が割り当てられているはずです。

# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:1a:4a:16:02:c7 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.62/25 brd 192.168.0.127 scope global eth0
       valid_lft forever preferred_lft forever
    inet 192.168.0.64/32 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::21a:4aff:fe16:2c7/64 scope link
       valid_lft forever preferred_lft forever

curlで確認しても、VIP: 192.168.0.64に対するリクエストがラウンドロビンでロードバランスされていることが分かるはずです。

# watch -n 0.1 curl 192.168.0.64

この状態で障害を想定し、haproxy-01のサービスhaproxyを停止します。

# systemctl stop haproxy

すると少ししてhaproxy-01からVIPが外れます。

# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:1a:4a:16:02:c7 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.62/25 brd 192.168.0.127 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::21a:4aff:fe16:2c7/64 scope link
       valid_lft forever preferred_lft forever

その後、haproxy-02にVIPが付与されます。(IPフェイルオーバ)

# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:1a:4a:16:02:c8 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.63/25 brd 192.168.0.127 scope global eth0
       valid_lft forever preferred_lft forever
    inet 192.168.0.64/32 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::21a:4aff:fe16:2c8/64 scope link
       valid_lft forever preferred_lft forever

VIP: 192.168.0.64に対するリクエストは引き続き処理できるようになっています。

# watch -n 0.1 curl 192.168.0.64

IPフェイルバックを試してみましょう。haproxy-01のサービスhaproxyを起動します。

# systemctl start haproxy

すると少ししてhaproxy-02からVIPが外れます。

# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:1a:4a:16:02:c8 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.63/25 brd 192.168.0.127 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::21a:4aff:fe16:2c8/64 scope link
       valid_lft forever preferred_lft forever

その後、haproxy-01にVIPが付与されます。(IPフェイルバック)

# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:1a:4a:16:02:c7 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.62/25 brd 192.168.0.127 scope global eth0
       valid_lft forever preferred_lft forever
    inet 192.168.0.64/32 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::21a:4aff:fe16:2c7/64 scope link
       valid_lft forever preferred_lft forever