Kubernetes in Containern aufsetzen


Kubernetes ist eine Management-Umgebung für Docker-Container, die eine einfache Skalierung ermöglicht und das Verschieben von Containern zwischen Hosts automatisiert. So könnte man es zumindest grob beschreiben. Mit ein paar zusätzlichen Mitteln kann man Kubernetes-Umgebungen so aufbauen, dass sie automatisch skalieren und Continuois Integration (CI) enorm einfach machen.

Sucht man im Netz nach Anleitungen wie man Kubernetes einrichtet, findet man zumeist Howtos, die die Kubernetes-Komponenten direkt auf den Hosts installieren. Doch warum nicht auch die Komponenten von Kubernetes in Containern laufen lassen, wenn wir doch eh schon Docker verwenden? Diese Anleitung soll daher betrachten, wie man Kubernetes möglichst unabhängig von der verwendeten Linux-Distribution aufsetzen kann.

Was wird gebraucht?

Um ein Kubernetes-Cluster aufzusetzen, benötigen wir mindestens einen Master-Server und einen oder mehrere Slave-Server, sogenannte Minions.

Die Komponenten, die zur Steuerung des Clusters notwendig sind, werden in ihrer Gesamtheit als Control Plane bezeichnet. Auf dem Master bestehen diese aus dem API-Server zur Steuerung des Clusters, dem Controller Manager für die Replikationsprozesse der Container und dem Scheduler, der die Container auf die einzelnen Minions verteilt, d.h. der die Workloads verteilt.

Auf den Minions benötigen wir einen Kubelet-Service, der die Container startet und einen Kube-Proxy, der als einfacher Netzwerk-Proxy und Loadbalancer fungiert.

Für eine zentrale Konfigurationsverwaltung wird etcd verwendet. Hier im Beispiel werden wir ihn auf dem Master bereitstellen.

Es ist durchaus möglich für eine höhere Redundanz den Master-Server mehrfach laufen zu lassen. Er muss dann hinter einem entsprechenden Loadbalancing sein. Ich werde hier aber nicht auf solche Details eingehen.

Damit unsere Container problemlos untereinander kommunizieren können, auch wenn sie auf verschiedenen Minions verteilt sind, müssen wir dafür sorgen, dass die Subnets der Nodes miteinander kommunizieren können. Für diese Aufgabe verwenden wir Flannel, das auf allen Hosts installiert wird.

Wollen wir, dass die Container auch mittels Namen untereinander kommunizieren können, können wir KubeDNS/SkyDNS verwenden. Dies ist allerdings optional und nicht zwingend notwendig. Für bestimmte Setups kann es aber notwendig sein.

In dieser Anleitung soll es erstmal darum gehen, wir wir eine Kubernetes-Umgebung mit einem Master und entsprechenden Minions umsetzen können, wobei ich voraussetze, dass die verwendete Linux-Distribution systemd einsetzt. Die hier in der Anleitung aufgeführten Service-Konfigurationen beziehen sich auf systemd. Wer ein anderes Init-System verwendet, muss die Konfigurationen entsprechend dafür anpassen.

Die Hosts vorbereiten

Die Hosts sollten sich innerhalb eines LANs befinden und logischerweise untereinander kommunizieren können. Es ist nicht notwendig, dass sie die gleiche IP-Range haben so lange sie sich untereinander erreichen und ungehindert miteinander kommunizieren können. Firewalls auf den Hosts sollte man zumindest für die LAN-Interfaces vermeiden.

Da wir Docker-Container nutzen wollen, muss natürlich auch Docker auf den Hosts laufen. Die Einrichtung dafür ist je nach Distribution unterschiedlich. Es gibt aber für ziemlich jede Distribution im Netz genügend Anleitungen, wie man Docker dort einrichtet.

Den Master einrichten

Zertifikate für die Authentifizierung und Verschlüsselung

Logischerweise wollen wir, dass unsere Services auf sichere Weise miteinander kommunizieren. Das bedeutet, dass wir eine Verschlüsselung benötigen. Glücklicherweise kann Kubernetes mit sowas umgehen. Wir benötigen dafür nur einige Zertifikate, die wir mit dem folgenden Skript generieren können:

#!/bin/bash

# This script generates the Certificate Authorities and server certs for etcd and Kubernetes

set -o errexit
set -o nounset
set -o pipefail

#
# Kubernetes
#

k8s_sans="DNS:cloud.mycompany.de,IP:<LAN-IP des Master hier rein>,IP:10.200.0.1,DNS:kubernetes,DNS:kubernetes.default,DNS:kubernetes.default.svc,DNS:kubernetes.default.svc.mycompany.local"
k8s_ca_dir=/opt/kubernetes/ca
k8s_cert_dir=/etc/kubernetes/ssl

# get EasyRSA
if [[ ! -d "${k8s_ca_dir}" ]]; then
  mkdir -p "${k8s_ca_dir}"
  curl -L -o /opt/EasyRSA.tgz https://github.com/OpenVPN/easy-rsa/releases/download/3.0.1/EasyRSA-3.0.1.tgz
  tar -xzf /opt/EasyRSA.tgz -C "${k8s_ca_dir}" --strip 1
  rm -f /opt/EasyRSA.tgz
fi

cd "${k8s_ca_dir}"
mkdir -p "${k8s_cert_dir}"

if [[ ! -d "./pki" ]]; then
  # create pki
  ./easyrsa init-pki
  ./easyrsa --batch --req-cn="MyCompany Kubernetes CA" build-ca nopass

  # generate certs
  ./easyrsa --subject-alt-name="${k8s_sans}" build-server-full apiserver nopass
  declare -r k8s_accounts=(kubelet kube-proxy)
  for account in ${k8s_accounts[@]}; do
    ./easyrsa build-client-full ${account} nopass
  done

  # copy client and server certs to kubernetes certs directory
  cp -p pki/ca.crt "${k8s_cert_dir}/"
  cp -p pki/issued/apiserver.crt "${k8s_cert_dir}/"
  cp -p pki/private/apiserver.key "${k8s_cert_dir}/"
  cp -p pki/issued/kubelet.crt "${k8s_cert_dir}/"
  cp -p pki/private/kubelet.key "${k8s_cert_dir}/"
  cp -p pki/issued/kube-proxy.crt "${k8s_cert_dir}/"
  cp -p pki/private/kube-proxy.key "${k8s_cert_dir}/"
fi

#
# etcd
#

etcd_sans="DNS:localhost,IP:127.0.0.1,IP:<LAN-IP des Master hier rein>"
etcd_ca_dir=/opt/etcd/ca
etcd_cert_dir=/etc/etcd/ssl

# get EasyRSA
if [[ ! -d "${etcd_ca_dir}" ]]; then
  mkdir -p "${etcd_ca_dir}"
  curl -L -o /opt/EasyRSA.tgz https://github.com/OpenVPN/easy-rsa/releases/download/3.0.1/EasyRSA-3.0.1.tgz
  tar -xzf /opt/EasyRSA.tgz -C "${etcd_ca_dir}" --strip 1
  rm -f /opt/EasyRSA.tgz
fi

cd "${etcd_ca_dir}"
mkdir -p "${etcd_cert_dir}"

if [[ ! -d "./pki" ]]; then
  # create pki
  ./easyrsa init-pki
  ./easyrsa --batch --req-cn="MyCompany etcd CA" build-ca nopass

  # generate certs
  ./easyrsa --subject-alt-name="${etcd_sans}" build-server-full server nopass
  ./easyrsa build-client-full client nopass

  # copy client and server certs to etcd certs directory
  cp -p pki/ca.crt "${etcd_cert_dir}/"
  cp -p pki/issued/server.crt "${etcd_cert_dir}/"
  cp -p pki/private/server.key "${etcd_cert_dir}/"
  cp -p pki/issued/client.crt "${etcd_cert_dir}/"
  cp -p pki/private/client.key "${etcd_cert_dir}/"
fi

Dieses Skript erstellt die notwendigen Zertifikate in /etc/kubernetes/ssl und /etc/etcd/ssl. Diese mounten wir dann später direkt in unsere Container um sie zu verwenden. Einige der Zertifikate, die Client-Zertifikate übertragen wir bei der Einrichtung der Minions auf die Minion-Hosts.

Kubelet

Beginnen wir auf dem Master mit der Einrichtung von Kubelet. Kubelet wird benötigt um unsere Container anhand von Manifest-Dateien lokal auszuführen. Verwendet unser System systemd, was bei immer mehr Linux-Distributionen der Fall ist, reicht eine einfache Konfiguration um den Kubelet-Service bereitszustellen. Wir legen dafür in /etc/systemd/system/kubelet.service die folgende Konfiguration an:

[Unit]
Description=Kubernetes Kubelet
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStartPre=/usr/bin/wget -q -N -P /usr/local/bin https://storage.googleapis.com/kubernetes-release/release/v1.2.3/bin/linux/amd64/kubelet
ExecStartPre=/bin/chmod +x /usr/local/bin/kubelet
ExecStartPre=/bin/mkdir -p /etc/kubernetes/manifests
ExecStart=/usr/local/bin/kubelet \
  --api-servers=http://127.0.0.1:8080 \
  --node-ip=<LAN-IP des Master hier rein> \
  --allow-privileged=true \
  --config=/etc/kubernetes/manifests \
  --hairpin-mode=hairpin-veth \
  --cluster-dns=10.200.0.2 \
  --cluster-domain=mycompany.cluster \
  --image-gc-high-threshold=70 \
  --image-gc-low-threshold=50 \
  --maximum-dead-containers=30 \
  --read-only-port=0 \
  --cadvisor-port=0
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Wie leicht zu ersehen ist, lädt diese Datei die Kubelet-Binary herunter, macht sie ausführbar und stellt den Ordner bereit, in dem wir später unsere Manifest-Dateien ablegen können um mit Hilfe von Kubelet neue Container auf dem Host zu starten. Abschliessend wird der Kubelet-Service gestartet. Zu beachten ist, dass beim Parameter –node-ip die LAN-IP des Master-Servers eingetragen werden muss.

Nun müssen wir den Service nur noch aktivieren und starten:

systemctl enable kubelet
systemctl start kubelet

etcd

Als nächstes kümmern wir uns um den zentralen Konfigurationsspeicher etcd. Wir wollen, dass dieser seine Daten auf dem Host ablegt, damit sie nicht verloren gehen, wenn wir mal den Container updaten. Also benötigen wir einen Ordner dafür:

mkdir /var/lib/etcd

Auf Systemen wie CentOS, die SELinux verwenden, müssen wir für diesen Ordner einen entsprechenden Security-Kontext setzen, damit eine VM bzw. ein Container in diesen schreiben kann:

mkdir --context=system_u:object_r:svirt_sandbox_file_t:s0 /var/lib/etcd

Nun müssen wir noch ein Manifest anlegen um den etcd in einem Container zu starten. Dieses legen wir in /etc/kubernetes/manifests/etcd.yml mit folgendem Inhalt an.

apiVersion: v1
kind: Pod
metadata:
  name: etcd
  namespace: kube-system
  labels:
    component: etcd
    role: server
spec:
  hostNetwork: true
  containers:
  - name: etcd
    image: quay.io/coreos/etcd:v2.2.5
    env:
    - name: ETCD_NAME
      value: etcd01
    - name: ETCD_DATA_DIR
      value: /data/etcd
    - name: ETCD_ADVERTISE_CLIENT_URLS
      value: https://<LAN-IP des Master hier rein>:2379
    - name: ETCD_LISTEN_CLIENT_URLS
      value: https://<LAN-IP des Master hier rein>:2379,https://127.0.0.1:2379
    - name: ETCD_INITIAL_ADVERTISE_PEER_URLS
      value: https://<LAN-IP des Master hier rein>:2380
    - name: ETCD_LISTEN_PEER_URLS
      value: https://<LAN-IP des Master hier rein>:2380,https://127.0.0.1:2380
    - name: ETCD_INITIAL_CLUSTER
      value: etcd01=https://<LAN-IP des Master hier rein>:2380
    - name: ETCD_CLIENT_CERT_AUTH
      value: "true"
    - name: ETCD_TRUSTED_CA_FILE
      value: /ssl/etcd/ca.crt
    - name: ETCD_KEY_FILE
      value: /ssl/etcd/server.key
    - name: ETCD_CERT_FILE
      value: /ssl/etcd/server.crt
    - name: ETCD_PEER_CLIENT_CERT_AUTH
      value: "true"
    - name: ETCD_PEER_KEY_FILE
      value: /ssl/etcd/server.key
    - name: ETCD_PEER_CERT_FILE
      value: /ssl/etcd/server.crt
    - name: ETCD_PEER_TRUSTED_CA_FILE
      value: /ssl/etcd/ca.crt
    volumeMounts:
    - mountPath: /data/etcd
      name: etcd-data-dir
    - mountPath: /ssl/etcd
      name: ssl-certs-etcd
      readOnly: true
    - mountPath: /etc/ssl/certs
      name: ssl-certs-host
      readOnly: true
  volumes:
  - hostPath:
      path: /var/lib/etcd
    name: etcd-data-dir
  - hostPath:
      path: /etc/etcd/ssl
    name: ssl-certs-etcd
  - hostPath:
      path: /usr/share/ca-certificates
    name: ssl-certs-host

Auch hier ist wieder zu beachten, dass die LAN-IP des Masters an den markierten Stellen eingefügt werden muss. Das wird in den folgenden Manifests und Konfigurationen noch häufiger der Fall sein. Also bitte nicht einfach alles ungesehen via Copy&Paste übernehmen. 😉

Sobald das Manifest angelegt wurde, wird das notwendige Image heruntergeladen und der Container gestartet.

API-Server

Nun benötigen wir unsere zentrale Verwaltung, den API-Server. Auch diesen können wir über ein einfaches Manifest starten, das wir in /etc/kubernetes/manifests/kube-apiserver.yml speichern.

apiVersion: v1
kind: Pod
metadata:
  name: kube-apiserver
  namespace: kube-system
spec:
  hostNetwork: true
  containers:
  - name: kube-apiserver
    image: gcr.io/google_containers/hyperkube:v1.2.3
    command:
    - /hyperkube
    - apiserver
    - --advertise-address=<LAN-IP des Master hier rein>
    - --etcd-servers=https://localhost:2379
    - --etcd-certfile=/ssl/etcd/client.crt
    - --etcd-keyfile=/ssl/etcd/client.key
    - --etcd-cafile=/ssl/etcd/ca.crt
    - --allow-privileged=true
    - --service-cluster-ip-range=10.200.0.0/16
    - --admission-control=NamespaceLifecycle,NamespaceExists,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota
    - --tls-cert-file=/ssl/kubernetes/apiserver.crt
    - --tls-private-key-file=/ssl/kubernetes/apiserver.key
    - --client-ca-file=/ssl/kubernetes/ca.crt
    volumeMounts:
    - mountPath: /etcd/config.json
      name: etcd-config
      readOnly: true
    - mountPath: /ssl/etcd
      name: ssl-certs-etcd
      readOnly: true
    - mountPath: /ssl/kubernetes
      name: ssl-certs-kubernetes
      readOnly: true
    - mountPath: /etc/ssl/certs
      name: ssl-certs-host
      readOnly: true
  volumes:
  - hostPath:
      path: /etc/kubernetes/etcd-config.json
    name: etcd-config
  - hostPath:
      path: /etc/etcd/ssl
    name: ssl-certs-etcd
  - hostPath:
      path: /etc/kubernetes/ssl
    name: ssl-certs-kubernetes
  - hostPath:
      path: /usr/share/ca-certificates
    name: ssl-certs-host

Auch hier heisst es nur kurz warten und unser Container läuft wie von Zauberhand und stellt uns auf Port 8080 den API-Server zur Verfügung.

Controller Manager

Die Einrichtung des Controller Managers ist genauso einfach. Wir erstellen einfach ein entsprechendes Manifest in /etc/kubernetes/manifests/kube-controller-manager.yml.

apiVersion: v1
kind: Pod
metadata:
  name: kube-controller-manager
  namespace: kube-system
spec:
  hostNetwork: true
  containers:
  - name: kube-controller-manager
    image: gcr.io/google_containers/hyperkube:v1.2.3
    command:
    - /hyperkube
    - controller-manager
    - --master=http://127.0.0.1:8080
    - --service-account-private-key-file=/ssl/kubernetes/apiserver.key
    - --root-ca-file=/ssl/kubernetes/ca.crt
    livenessProbe:
      httpGet:
        host: 127.0.0.1
        path: /healthz
        port: 10252
      initialDelaySeconds: 15
      timeoutSeconds: 1
    volumeMounts:
    - mountPath: /ssl/kubernetes
      name: ssl-certs-kubernetes
      readOnly: true
    - mountPath: /etc/ssl/certs
      name: ssl-certs-host
      readOnly: true
  volumes:
  - hostPath:
      path: /etc/kubernetes/ssl
    name: ssl-certs-kubernetes
  - hostPath:
      path: /usr/share/ca-certificates
    name: ssl-certs-host

Kurz warten und unser Controller Manager läuft in einem Docker-Container.

Scheduler

Für den Scheduler erstellen wir ebenfalls ein Manifest, diesmal in /etc/kubernetes/manifests/kube-scheduler.yml.

apiVersion: v1
kind: Pod
metadata:
  name: kube-scheduler
  namespace: kube-system
spec:
  hostNetwork: true
  containers:
  - name: kube-scheduler
    image: gcr.io/google_containers/hyperkube:v1.2.3
    command:
    - /hyperkube
    - scheduler
    - --master=http://127.0.0.1:8080
    livenessProbe:
      httpGet:
        host: 127.0.0.1
        path: /healthz
        port: 10251
      initialDelaySeconds: 15
      timeoutSeconds: 1

Kubelet CLI

Natürlich müssen wir das Cluster irgendwie verwalten können. Die Verwaltung findet zentral auf dem Master-Server statt. Dafür benötigen wir ein CLI für Kubelet, das kubectl genannt wird. Dieses installieren wir wie folgt:

wget -q -N -P /usr/local/bin https://storage.googleapis.com/kubernetes-release/release/v1.2.3/bin/linux/amd64/kubectl
chmod +x /usr/local/bin/kubectl

Abschliessende Vorbereitungen

Damit läuft unser Master grundlegend. Wir müssen nun nur noch ein paar letzte Vorbereitungen machen.

Zuerst erstellen wir einen Namespace für unser Cluster:

kubectl create namespace kube-system

Theoretisch kann auch unser Master Pods ausführen. Das will man üblicherweise nicht, weswegen wir ihn vom Scheduling ausschliessen sollten:

kubectl cordon <hostname>

Und das war’s auch schon. Nun müssen wir nur noch die Minions und Flannel einrichten. Dem aufmerksamen Admin wird nicht entgangen sein, dass mit allen Services 2 Container gestartet wurden, wobei einer nur einen Pause-Befehl ausführt. Diese Container sind dafür zuständig dass unsere Master-Services nicht zu schnell laufen und die CPU wegfressen.

Einen Minion einrichten

Die folgende Prozedur muss auf allen Minions durchgeführt werden. Natürlich ist auch hier wieder die Voraussetzung, dass Docker zur Verfügung steht und die Firewall sollte für das LAN-Interface jeglichen Traffic durchlassen.

Client-Zertifikate

Für die verschlüsselte Kommunikation und Authentifizierung benötigen wir die Client-Zertifikate, die wir auf dem Master generiert hatten. Folgende Zertifikate müssen auf den Minion übertragen werden:

  • /etc/kubernetes/ssl/ca.crt
  • /etc/kubernetes/ssl/kubelet.crt
  • /etc/kubernetes/ssl/kubelet.key
  • /etc/kubernetes/ssl/kube-proxy.crt
  • /etc/kubernetes/ssl/kube-proxy.key
  • /etc/etcd/ssl/ca.crt
  • /etc/etcd/ssl/client.crt
  • /etc/etcd/ssl/client.key

Sie werden auf dem Minion in den gleichen Ordnern abgelegt, wie auf dem Master. Die Ordner müssen bei Bedarf angelegt werden:

mkdir -p /etc/kubernetes/ssl
mkdir -p /etc/etcd/ssl

Unser Minions sollen logischerweise Pods ausführen können. Dafür benötigen wir wieder einmal Kubelet, für den wir unser Host vorbereiten müssen.

mkdir /var/lib/kubelet
mkdir /var/lib/kubelet/pods

Bei Hosts mit SELinux (z.B. bei CentOS) müssen wir dem zweiten Befehl wieder einen Security-Context hinzufügen, damit VMs/Container darin schreiben können.

mkdir --context=system_u:object_r:svirt_sandbox_file_t:s0 /var/lib/kubelet/pods

Auf den Minions müssen wir Kubelet auch noch etwas konfigurieren. Hierfür legen wir eine Konfiguration in /etc/kubernetes/kubelet-kubeconfig.yml an:

apiVersion: v1
kind: Config
users:
- name: kubelet
  user:
    client-certificate: /etc/kubernetes/ssl/kubelet.crt
    client-key: /etc/kubernetes/ssl/kubelet.key
clusters:
- name: k8s
  cluster:
    certificate-authority: /etc/kubernetes/ssl/ca.crt
contexts:
- name: k8s-context
  context:
    cluster: k8s
    user: kubelet
current-context: k8s-context

Nun benötigen wir nur noch unsere systemd-Konfiguration um Kubelet als Service zu starten. Diese speichern wir wieder in /etc/systemd/system/kubelet.service.

[Unit]
Description=Kubernetes Kubelet
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStartPre=/usr/bin/wget -q -N -P /usr/local/bin https://storage.googleapis.com/kubernetes-release/release/v1.2.3/bin/linux/amd64/kubelet
ExecStartPre=/bin/chmod +x /usr/local/bin/kubelet
ExecStartPre=/bin/mkdir -p /etc/kubernetes/manifests
ExecStart=/usr/local/bin/kubelet \
  --api-servers=https://<LAN-IP des Master hier rein>:6443 \
  --node-ip=set private node IP here \
  --kubeconfig=/etc/kubernetes/kubelet-kubeconfig.yml \
  --allow-privileged=true \
  --config=/etc/kubernetes/manifests \
  --hairpin-mode=hairpin-veth \
  --cluster-dns=10.200.0.2 \
  --cluster-domain=mycompany.cluster \
  --image-gc-high-threshold=70 \
  --image-gc-low-threshold=50 \
  --maximum-dead-containers=30 \
  --read-only-port=0 \
  --cadvisor-port=0
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Wir verwenden hier nicht den Port 8080 des Masters sondern 6443, da dort die verschlüsselte Kommunikation (HTTPS) bereitgestellt wird. Über Port 8080 ist nur eine unverschlüsselte Kommunikation (HTTP) möglich.

Logischerweise müssen wir auch diesen Service wieder aktivieren und starten:

systemctl enable kubelet
systemctl start kubelet

Nun benötigt unser Minion nur noch den Kube-Proxy. Seine Konfiguration legen wir in /etc/kubernetes/proxy-kubeconfig.yml an:

apiVersion: v1
kind: Config
users:
- name: kube-proxy
  user:
    client-certificate: /ssl/kubernetes/kube-proxy.crt
    client-key: /ssl/kubernetes/kube-proxy.key
clusters:
- name: k8s
  cluster:
    certificate-authority: /ssl/kubernetes/ca.crt
contexts:
- name: k8s-context
  context:
    cluster: k8s
    user: kube-proxy
current-context: k8s-context

Starten tun wir ihn logischerweise wieder als Container mittels Kubelet. Dafür legen wir ein Manifest mit folgendem Inhalt in /etc/kubernetes/manifests/kube-proxy.yml an:

apiVersion: v1
kind: Pod
metadata:
  name: kube-proxy
  namespace: kube-system
spec:
  hostNetwork: true
  containers:
  - name: kube-proxy
    image: gcr.io/google_containers/hyperkube:v1.2.3
    command:
    - /hyperkube
    - proxy
    - --master=https://<LAN-IP des Master hier rein>:6443
    - --kubeconfig=/conf/kubeconfig
    - --proxy-mode=iptables
    securityContext:
      privileged: true
    volumeMounts:
    - mountPath: /conf/kubeconfig
      name: kubeconfig
      readOnly: true
    - mountPath: /ssl/kubernetes
      name: ssl-certs-kubernetes
      readOnly: true
    - mountPath: /etc/ssl/certs
      name: ssl-certs-host
      readOnly: true
  volumes:
  - hostPath:
      path: /etc/kubernetes/proxy-kubeconfig.yml
    name: kubeconfig
  - hostPath:
      path: /etc/kubernetes/ssl
    name: ssl-certs-kubernetes
  - hostPath:
      path: /usr/share/ca-certificates
    name: ssl-certs-host

Nach kurzem Warten ist unser Minion bereit um Aufgaben vom Master zugeteilt zu bekommen. Wir können das überprüfen indem wir uns auf dem Master die Liste der verfügbaren Nodes anschauen:

kubectl get nodes

Flannel

Für unser Docker-Netzwerk benutzen wir das Subnetz 10.100.0.0/16, wobei jeder Node innerhalb dieser Range ein /24-Subnetz zugeteilt bekommt. Um dies unserem etcd mitzuteilen, müssen wir auf irgendeinem unserer Nodes folgenden Befehl ausführen:

docker run --rm --entrypoint=/etcdctl --net=host \
 -v /etc/etcd/ssl:/ssl:ro \
 quay.io/coreos/etcd:v2.2.5 \
 --endpoint https://<LAN-IP des Master hier rein>:2379 \
 --key-file /ssl/client.key \
 --cert-file /ssl/client.crt \
 --ca-file /ssl/ca.crt \
 set /flannel/config '{"Network": "10.100.0.0/16", "Backend": {"Type": "host-gw"}}'

Nun müssen wir auf jedem unserer Nodes noch einen Container für Flannel starten. Auch hier nutzen wir wieder ein Manifest, diesmal in /etc/kubernetes/manifests/flannel.yml.

apiVersion: v1
kind: Pod
metadata:
  name: flannel
  namespace: kube-system
  labels:
    component: flannel
    role: server
spec:
  hostNetwork: true
  containers:
  - name: flannel
    image: quay.io/coreos/flannel:0.5.5
    env:
    - name: FLANNELD_IFACE
      value: <LAN-Interface hier rein>
    - name: FLANNELD_ETCD_PREFIX
      value: /flannel
    - name: FLANNELD_ETCD_ENDPOINTS
      value: https://<LAN-IP des Master hier rein>:2379
    - name: FLANNELD_ETCD_CAFILE
      value: /ssl/etcd/ca.crt
    - name: FLANNELD_ETCD_KEYFILE
      value: /ssl/etcd/client.key
    - name: FLANNELD_ETCD_CERTFILE
      value: /ssl/etcd/client.crt
    securityContext:
      privileged: true
    volumeMounts:
    - mountPath: /run/flannel
      name: flannel-out
    - mountPath: /ssl/etcd
      name: ssl-certs-etcd
      readOnly: true
    - mountPath: /etc/ssl/certs
      name: ssl-certs-host
      readOnly: true
  volumes:
  - hostPath:
      path: /etc/flannel
    name: flannel-out
  - hostPath:
      path: /etc/etcd/ssl
    name: ssl-certs-etcd
  - hostPath:
      path: /usr/share/ca-certificates
    name: ssl-certs-host

Bitte beachten, dass das LAN-Interface in diesem Manifest eingetragen werden muss, also bspw: eth0

Ob alle Subnets korrekt konfiguriert wurden, kann mit folgendem Befehl überprüft werden, den wir auf irgendeinem unserer Nodes ausführen können:

docker run --rm --entrypoint=/etcdctl --net=host \
 -v /etc/etcd/ssl:/ssl:ro \
 quay.io/coreos/etcd:v2.2.5 \
 --endpoint https://<LAN-IP des Master hier rein>:2379 \
 --key-file /ssl/client.key \
 --cert-file /ssl/client.crt \
 --ca-file /ssl/ca.crt \
 ls /flannel/subnets

Er sollte uns so viele Subnetze anzeigen wie wir Nodes im Cluster haben.

Damit auch unsere Docker-Container immer die korrekten Subnetze nutzen, müssen wir Docker rekonfigurieren. Dafür generieren wir auf jedem Node eine Datei mit den Docker-Optionen:

docker run --rm -v /etc/flannel:/run/flannel \
 quay.io/coreos/flannel:0.5.5 \
 /opt/bin/mk-docker-opts.sh -d /run/flannel/docker_opts.env -i

Damit Docker diese Parameter auch nutzt, legen wir in /etc/systemd/system/docker.service.d/flannel.conf folgende Konfiguration an:

[Service]
EnvironmentFile=/etc/flannel/docker_opts.env
ExecStart=
ExecStart=/bin/sh -c '/usr/bin/docker daemon $OPTIONS \
          $DOCKER_OPT_BIP \
          $DOCKER_OPT_MTU \
          $DOCKER_OPT_IPMASQ \
          $DOCKER_STORAGE_OPTIONS \
          $DOCKER_NETWORK_OPTIONS \
          $ADD_REGISTRY \
          $BLOCK_REGISTRY \
          $INSECURE_REGISTRY \
          2>&1 | /usr/bin/forward-journald -tag docker'

Abschliessend noch die systemd-Konfiguration neu laden und Docker restarten:

systemctl daemon-reload
systemctl restart docker

Und schon läuft unser Kubernetes Cluster.

KubeDNS/SkyDNS

Wer einen DNS ins seinem Cluster nutzen will, der kann folgendes Manifest nutzen um einen entsprechenden Kubernetes-Service zu starten. Bitte darauf achten, dass dieses Manifest diesmal nicht in /etc/kubernetes/manifests/ angelegt wird. Man kann es z.B. in seinem Home-Verzeichnis ablegen oder an einem anderen Ort, wo man seine Kubernetes-Manifests sammeln möchte.

---
apiVersion: v1
kind: Service
metadata:
  name: kube-dns
  namespace: kube-system
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: "KubeDNS"
spec:
  selector:
    k8s-app: kube-dns
  clusterIP:  10.200.0.2
  ports:
  - name: dns
    port: 53
    protocol: UDP
  - name: dns-tcp
    port: 53
    protocol: TCP
---
apiVersion: v1
kind: ReplicationController
metadata:
  name: kube-dns-v11
  namespace: kube-system
  labels:
    k8s-app: kube-dns
    version: v11
    kubernetes.io/cluster-service: "true"
spec:
  replicas: 2
  selector:
    k8s-app: kube-dns
    version: v11
  template:
    metadata:
      labels:
        k8s-app: kube-dns
        version: v11
        kubernetes.io/cluster-service: "true"
    spec:
      containers:
      - name: etcd
        image: gcr.io/google_containers/etcd-amd64:2.2.1
        resources:
          limits:
            cpu: 100m
            memory: 500Mi
          requests:
            cpu: 100m
            memory: 50Mi
        command:
        - /usr/local/bin/etcd
        - -data-dir
        - /var/etcd/data
        - -listen-client-urls
        - http://127.0.0.1:2379,http://127.0.0.1:4001
        - -advertise-client-urls
        - http://127.0.0.1:2379,http://127.0.0.1:4001
        - -initial-cluster-token
        - skydns-etcd
        volumeMounts:
        - name: etcd-storage
          mountPath: /var/etcd/data
      - name: kube2sky
        image: gcr.io/google_containers/kube2sky:1.14
        resources:
          limits:
            cpu: 100m
            # Kube2sky watches all pods.
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 50Mi
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 60
          timeoutSeconds: 5
          successThreshold: 1
          failureThreshold: 5
        readinessProbe:
          httpGet:
            path: /readiness
            port: 8081
            scheme: HTTP
          initialDelaySeconds: 30
          timeoutSeconds: 5
        args:
        - --domain=mycompany.cluster
      - name: skydns
        image: gcr.io/google_containers/skydns:2015-10-13-8c72f8c
        resources:
          limits:
            cpu: 100m
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 50Mi
        args:
        # command = "/skydns"
        - -machines=http://127.0.0.1:4001
        - -addr=0.0.0.0:53
        - -ns-rotate=false
        - -domain=mycompany.cluster.
        ports:
        - containerPort: 53
          name: dns
          protocol: UDP
        - containerPort: 53
          name: dns-tcp
          protocol: TCP
      - name: healthz
        image: gcr.io/google_containers/exechealthz:1.0
        resources:
          limits:
            cpu: 10m
            memory: 20Mi
          requests:
            cpu: 10m
            memory: 20Mi
        args:
        - -cmd=nslookup kubernetes.default.svc.mycompany.cluster 127.0.0.1 >/dev/null
        - -port=8080
        ports:
        - containerPort: 8080
          protocol: TCP
      volumes:
      - name: etcd-storage
        emptyDir: {}
      dnsPolicy: Default

Das Manifest können wir mit folgendem Befehl starten:

kubectl create -f dns.yaml

Kubernetes kümmert sich darum, dass mindestens 2 Instanzen von unserem DNS laufen.

Abschliessende Worte zum Kubernetes-Cluster

Damit läuft unser Kubernetes-Cluster endlich vollständig. Wir können nun damit beginnen es zu nutzen und mit Manifests zu füttern.

Will man das Cluster aktualisieren, reicht es aus die Versionsnummern in den Manifests anzupassen und den Kubelet-Service neuzustarten. Nicht vergessen die systemd-Konifguration mittels ‚daemon-reload‘ neuzuladen. Kubectl kann man einfach durch den hier aufgeführten wget-Befehl aktualisieren, wobei auch wiederum nur die Versionsnummer entsprechend ersetzt werden muss.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.