Jahrelang hatte ich physische Server mit Standardfunktionen wie Apache, Bind, MySQL und PHP. Gelegentlich migriere ich einen Server, wenn ein anderer Anbieter einen besseren Preis anbietet. Updates können schwer sein, wenn auf dem Server mehrere Websites gehostet werden, da manche Server auf bestimmte Versionen von PHP angewiesen sind. Deshalb behalte ich manche Server oft länger, um die Probleme einer Migration zu vermeiden. Das kommt Ihnen vielleicht vertraut vor.
Ich habe die Entwicklung von Docker schon eine Weile verfolgt und wusste, dass meine Probleme sich durch eine Migration hin zu Containern auflösen würden. Das war, als ich von drei in Frankreich gehosteten Gentoo-Servern auf zwei Debian-Servern in den USA und einen in Frankreich gehosteten Gentoo-Servern umgestiegen bin.
Heute erkläre ich die Installation von Docker und die Verwaltung von Containern mithilfe von Kubernetes.
Für die Verwaltung von Containern gibt es verschiedene Systeme: Amazon EC2 Container Service, Rancher, Kubernetes usw. Ich habe Kubernetes gewählt, da es in unterschiedlichen Umgebungen installiert werden kann und Sie nicht an einen Anbieter gebunden sind. Bei Nuxeo verwenden wir ebenfalls Kubernetes.
Installation von Docker
Der erste Schritt ist ziemlich leicht.
Installation von Docker auf Debian:
apt-get install docker.io
Installation von Docker auf Gentoo:
emerge -v docker
Im nächsten Schritt wird Kubernetes installiert.
Installation von Kubernetes
Wenn Sie einen einfachen Docker-Installationsvorgang verwenden, haben Sie kein Cluster und verlieren die Vorteile von Kubernetes.
Mehrere Docker-Server sind die bessere Lösung. Zunächst wird ein Docker-Dienst verwendet, um etcd und flannel zu starten. Dadurch wird das freigegebene Netzwerk aktiviert und Kubernetes kann seine Konfigurationsinformationen speichern und freigeben.
[email protected]:/home/shared# etcdctl member list
eacd7f155934262: name=b5.loopingz.com peerURLs=http://91.121.82.118:2380 clientURLs=http://91.121.82.118:2379,http://91.121.82.118:4001
2f0f8b2f17fffe3c: name=c2.loopingz.com peerURLs=http://198.245.51.134:2380 clientURLs=http://198.245.51.134:2379,http://198.245.51.134:4001
88314cdfe9bc1797: name=default peerURLs=http://142.4.214.129:2380 clientURLs=http://142.4.214.129:2379,http://142.4.214.129:4001
Jetzt haben Sie verschiedene Netzwerke:
- 0.0.0/16 stellt die Kubernetes-Dienste/-Load Balancer dar
- 1.0.0/16: hier werden die Pods erstellt. Jeder Knoten hat 10.1.xx.0/24 für seine Pods
flannel.1 Link encap:Ethernet HWaddr c2:67:be:06:2c:11
inet addr:10.1.72.0 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: fe80::c067:beff:fe06:2c11/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1
RX packets:5412360 errors:0 dropped:0 overruns:0 frame:0
TX packets:4174530 errors:0 dropped:21 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:1651767659 (1.5 GiB) TX bytes:3453452284 (3.2 GiB)
Installation von GlusterFS
Abhängig von Ihrer Pod-Konfiguration und der Knotenauswahl können die Container in jedem Knoten im Cluster erstellt werden. Das bedeutet, dass Sie den Speicher zwischen den Knoten aufteilen müssen. Sie haben verschiedene Lösungen, wie Amazon EFS (Beta ), Google Cloud Storage, GlusterFS usw.
GlusterFS ist eine offene Lösung für gemeinsam genutzte Speicher. Es läuft als Daemon und verwendet UDP-Port 24007. In meinem nächsten Blog gehe ich mehr auf die Einzelheiten der Installation von GlusterFS und der von mir eingerichteten Vergleichswerte ein.
Installation von Firewallregeln
Um die Firewall auf allen Servern gleichzeitig zu aktualisieren, habe ich ein kleines Shell-Script erstellt, das auf Änderungen am etcd-Knoten reagiert und die Firewall entsprechend den Regeln und Cluster-Knoten aktualisiert.
Aktualisierung der Firewall-Konfiguration
Die Firewall wird durch die Datei fw.conf konfiguriert, die auf einem GlusterFSVolume freigegeben ist
#!/bin/sh
MD5_TARGET=`md5sum /home/shared/configs/firewall/fw.conf | awk '{print $1}'`
MD5_NEW=`md5sum fw.conf | awk '{print $1}'`
if [ "$MD5_TARGET" == "$MD5_NEW" ]; then
echo "Not config change"
exit 0
fi
cp fw.conf /home/shared/configs/firewall/
etcdctl set /cluster/firewall/update $MD5_NEW
Beim CURL-Befehl mit ?wait=true tritt eine Zeitüberschreitung nur ein, wenn der Wert durch das obige Skript geändert wird. Die Firewall auf dem Host wird dann aktualisiert:
while :
do
curl -L http://127.0.0.1:4001/v2/keys/cluster/firewall/update?wait=true
NEW_HASH=`etcdctl get /cluster/firewall/update`
if [ "$NEW_HASH" != "$FW_HASH" ]; then
echo "Update the firewall"
source /usr/local/bin/firewall_builder
FW_HASH=$NEW_HASH
fi
done
Installation des Docker-Repository
Um die Containerdefinition für die Verwendung mit Kubernetes zu speichern, ist ein eigenes Containerrepository von Vorteil.
Verwenden wir also Kubernetes für die Bereitstellung unseres ersten Pod:
Das Standardformat ist YAML, aber mir persönlich ist JSON lieber.
Hier ist das Format docker-rc.json
{
"apiVersion": "v1",
"kind": "ReplicationController",
"metadata": {
"name": "docker-repository",
"labels": {
"app": "docker-repository",
"version": "v1"
}
},
"spec": {
"replicas": 1,
"selector": {
"app": "docker-repository",
"version": "v1"
},
"template": {
"metadata": {
"labels": {
"app": "docker-repository",
"version": "v1"
}
},
"spec": {
"volumes": [
{
"name": "config",
"hostPath": { "path": "/home/shared/configs/docker" }
},{
"name": "data",
"hostPath": { "path": "/home/shared/docker" }
}
],
"containers": [
{
"name": "registry",
"image": "registry:2.2.1",
"volumeMounts": [{"name":"config", "mountPath":"/etc/docker/"},{"name":"data", "mountPath": "/var/lib/registry
"}],
"resources": {
"limits": {
"cpu": "100m",
"memory": "50Mi"
},
"requests": {
"cpu": "100m",
"memory": "50Mi"
}
},
"ports": [
{
"containerPort": 5000
}
]
}
]
}
}
}
}
Und hier ist das Format docker-svc.json
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"name": "docker-repository",
"labels": {
"app": "docker-repository"
}
},
"spec": {
"type": "LoadBalancer",
"selector": {
"app": "docker-repository"
},
"clusterIP": "10.0.0.204",
"ports": [
{
"protocol": "TCP",
"port": 5000,
"targetPort": 5000
}
]
}
}
Installation des NGINX-Proxy
Um verschiedene Domänen auf Ihrem Server hosten zu können, wird der Reverseproxy benötigt. Die Installation des NGINX ist einfach. Achten Sie jedoch darauf, dass Sie einige Header hinzufügen müssen:
Bei mit ist der erste Host docker.loopingz.com
server {
listen 443;
server_name docker.loopingz.com;
access_log /var/log/nginx/docker.loopingz.com_access_log main;
error_log /var/log/nginx/docker.loopingz.com_error_log info;
client_max_body_size 0;
chunked_transfer_encoding on;
location / {
include /etc/nginx/conf.d/dev-auth;
proxy_pass http://10.0.0.204:5000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
add_header 'Docker-Distribution-Api-Version' 'registry/2.0' always;
}
ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA:!CAMELLIA;
ssl_prefer_server_ciphers on;
ssl_certificate /etc/letsencrypt/live/docker.loopingz.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/docker.loopingz.com/privkey.pem;
}
Sie sehen in dieser Datei, dass wir die IP 10.0.0.204 verwenden. Dabei handelt es sich um die in docker-svc.json definierte Cluster-IP. Kubernetes ordnet diese IP unseren Docker-Registrierungscontainern zu.
Ich möchte, dass NGINX auf allen Knoten des Clusters bereitgestellt wird, sodass ein http-/https-Eintrag von allen Knoten aus auf den Cluster zeigt. Definieren wir also den replicationController von NGINX.
{
"apiVersion": "v1",
"kind": "ReplicationController",
"metadata": {
"name": "nginx",
"labels": {
"app": "nginx",
"version": "v1"
}
},
"spec": {
"replicas": 3,
"selector": {
"app": "nginx",
"version": "v1"
},
"template": {
"metadata": {
"labels": {
"app": "nginx",
"version": "v1"
}
},
"spec": {
"volumes": [
{
"name": "config",
"hostPath": { "path": "/home/shared/configs/nginx/" }
},{
"name": "logs",
"hostPath": { "path": "/home/shared/logs/nginx/" }
},{
"name": "certs",
"hostPath": { "path": "/home/shared/letsencrypt/" }
},{
"name": "static",
"hostPath": { "path": "/home/shared/nginx/" }
}
],
"containers": [
{
"name": "nginx",
"image": "nginx:latest",
"volumeMounts": [{"name": "static", "readOnly": true, "mountPath": "/var/www/"},{"name": "certs", "readOnly": true, "mountPath": "/etc/letsencrypt"}, {"name":"logs", "mountPath":"/var/log/nginx/"},{"name":"config", "readOnly": true, "mountPath":"/etc/nginx/conf.d/"}],
"resources": {
"limits": {
"cpu": "100m",
"memory": "50Mi"
},
"requests": {
"cpu": "100m",
"memory": "50Mi"
}
},
"ports": [
{
"containerPort": 80,
"hostPort": 80
},{
"containerPort": 443,
"hostPort": 443
}
]
}
]
}
}
}
}
Let’s Encrypt
Zur Aktivierung von SSL auf allen vHosts verwende ich Let’s Encrypt. Dadurch erhalten Sie ein dreimonatiges kostenloses SSL-Zertifikat. Der Bestellvorgang ist automatisiert, sodass alle NGINX-Hosts wie folgt konfiguriert sind:
location /.well-known/acme-challenge {
add_header "Content-Type:" "application/jose+json" always;
root /etc/nginx/conf.d;
}
Testen Sie selbst, nachdem Sie jetzt ja alles über die Installation von Kubernetes wissen! Als nächstes werde ich über das Benchmarking von GlusterFS und das Erstellen neuer Services schreiben, bleiben Sie also dran!