Introduction

Dans cet article nous allons voir comment provisionner un cluster avec ou sans haute disponibilité sur HCloud.
Sans haute disponibilité, on va créer un cluster contenant 2 noeuds (1 master et 1 worker). C’est parfait quand on n’est pas en production.

Pré-requis

Déploiement du cluster

Créer un compte sur HCloud (entrez vos informations personnelles et surtout une CB)

Ensuite créez un projet sur HCloud.

Une fois le projet créé, créer un token API et gardez le précieusement.

Ensuite entrer les commandes suivantes:

1
2
3
4
5
6
7
8
# Doc: https://github.com/xetys/hetzner-kube/blob/master/docs/cluster-create.md

# Create context
hetzner-kube context add demo
# Create SSH key
hetzner-kube ssh-key add --name macbook
# Create Cluster
hetzner-kube cluster create --name demo --ssh-key macbook --datacenters nbg1-dc3 --worker-server-type cx21 --master-server-type cx11 --worker-count 1

Pour avoir un cluster en HA, il suffit de passer le paramètre --ha-enabled à la commande précédente. Il y aura alors 3 masters nodes.

La commande suivante va permettre de créer un contexte Kubernetes “[email protected]” sur votre laptop dans ~/.kube/config

1
2
#hetzner-kube cluster kubeconfig <cluster-name> -f
hetzner-kube cluster kubeconfig demo -f

Il sera ensuite possible de lister les noeuds du cluster:

1
2
3
4
5
kubectl get nodes

NAME STATUS ROLES AGE VERSION
demo-master-01 Ready master 1h v1.9.6
demo-worker-01 Ready <none> 1h v1.9.6

Installation d’OpenEBS

Installation de l’operator et de la storageclass:

1
2
3
kubectl apply -f https://raw.githubusercontent.com/openebs/openebs/master/k8s/openebs-operator.yaml

kubectl apply -f https://raw.githubusercontent.com/openebs/openebs/master/k8s/openebs-storageclasses.yaml

On définit la nouvelle storageclass comme classe par défaut:

1
kubectl patch storageclass openebs-standard -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

Installation d’un Ingress Controller

Traefik Ingress

1. Création des Rôles (RBAC):

1
kubectl apply -f   https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/traefik-rbac.yaml

Contenu du fichier distant traefik-rbac.yaml :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: traefik-ingress-controller
rules:
- apiGroups:
- ""
resources:
- services
- endpoints
- secrets
verbs:
- get
- list
- watch
- apiGroups:
- extensions
resources:
- ingresses
verbs:
- get
- list
- watch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: traefik-ingress-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: traefik-ingress-controller
subjects:
- kind: ServiceAccount
name: traefik-ingress-controller
namespace: kube-system

2. Déployement de traefik via Deployment ou DaemonSet

Option 1: via Deployment (permet de créer un nodePort)

1
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/traefik-deployment.yaml

Contenu du fichier distant traefik-deployment.yaml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: traefik-ingress-controller
namespace: kube-system
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: traefik-ingress-controller
namespace: kube-system
labels:
k8s-app: traefik-ingress-lb
spec:
replicas: 1
selector:
matchLabels:
k8s-app: traefik-ingress-lb
template:
metadata:
labels:
k8s-app: traefik-ingress-lb
name: traefik-ingress-lb
spec:
serviceAccountName: traefik-ingress-controller
terminationGracePeriodSeconds: 60
containers:
- image: traefik
name: traefik-ingress-lb
ports:
- name: http
containerPort: 80
- name: admin
containerPort: 8080
args:
- --api
- --kubernetes
- --logLevel=INFO
---
kind: Service
apiVersion: v1
metadata:
name: traefik-ingress-service
namespace: kube-system
spec:
selector:
k8s-app: traefik-ingress-lb
ports:
- protocol: TCP
port: 80
name: web
- protocol: TCP
port: 8080
name: admin
type: NodePort

Option 2: via DaemonSet (sera indispensable pour la suite du tuto)

1
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/traefik-ds.yaml

Contenu du fichier distant traefik-ds.yaml :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: traefik-ingress-controller
namespace: kube-system
---
kind: DaemonSet
apiVersion: extensions/v1beta1
metadata:
name: traefik-ingress-controller
namespace: kube-system
labels:
k8s-app: traefik-ingress-lb
spec:
template:
metadata:
labels:
k8s-app: traefik-ingress-lb
name: traefik-ingress-lb
spec:
serviceAccountName: traefik-ingress-controller
terminationGracePeriodSeconds: 60
containers:
- image: traefik
name: traefik-ingress-lb
ports:
- name: http
containerPort: 80
hostPort: 80
- name: admin
containerPort: 8080
hostPort: 8080
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
args:
- --api
- --kubernetes
- --logLevel=INFO
---
kind: Service
apiVersion: v1
metadata:
name: traefik-ingress-service
namespace: kube-system
spec:
selector:
k8s-app: traefik-ingress-lb
ports:
- protocol: TCP
port: 80
name: web
- protocol: TCP
port: 8080
name: admin

3. Vérification du déploiement

1
kubectl --namespace=kube-system get pods

4. Déploiement de l’interface d’admin de Traefik

Via un service:

1
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/ui.yaml

Contenu du fichier distant ui.yaml :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
---
apiVersion: v1
kind: Service
metadata:
name: traefik-web-ui
namespace: kube-system
spec:
selector:
k8s-app: traefik-ingress-lb
ports:
- name: web
port: 80
targetPort: 8080
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: traefik-web-ui
namespace: kube-system
spec:
rules:
- host: traefik-ui.minikube
http:
paths:
- path: /
backend:
serviceName: traefik-web-ui
servicePort: web

5. Accès à l’interface Traefik en dev

1
echo "ip_dun_worker traefik-ui.minikube" | sudo tee -a /etc/hosts

6. Vérification du bon fonctionnement de l’ingress

6.1. Déploiement de 3 apps de test

1
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheese-deployments.yaml

Contenu du fichier distant cheese-deployments.yaml :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: stilton
labels:
app: cheese
cheese: stilton
spec:
replicas: 2
selector:
matchLabels:
app: cheese
task: stilton
template:
metadata:
labels:
app: cheese
task: stilton
version: v0.0.1
spec:
containers:
- name: cheese
image: errm/cheese:stilton
resources:
requests:
cpu: 100m
memory: 50Mi
limits:
cpu: 100m
memory: 50Mi
ports:
- containerPort: 80
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: cheddar
labels:
app: cheese
cheese: cheddar
spec:
replicas: 2
selector:
matchLabels:
app: cheese
task: cheddar
template:
metadata:
labels:
app: cheese
task: cheddar
version: v0.0.1
spec:
containers:
- name: cheese
image: errm/cheese:cheddar
resources:
requests:
cpu: 100m
memory: 50Mi
limits:
cpu: 100m
memory: 50Mi
ports:
- containerPort: 80
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: wensleydale
labels:
app: cheese
cheese: wensleydale
spec:
replicas: 2
selector:
matchLabels:
app: cheese
task: wensleydale
template:
metadata:
labels:
app: cheese
task: wensleydale
version: v0.0.1
spec:
containers:
- name: cheese
image: errm/cheese:wensleydale
resources:
requests:
cpu: 100m
memory: 50Mi
limits:
cpu: 100m
memory: 50Mi
ports:
- containerPort: 80

6.2. Création des 3 services

1
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheese-services.yaml

Contenu du fichier distant cheese-services.yaml :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
---
apiVersion: v1
kind: Service
metadata:
name: stilton
spec:
ports:
- name: http
targetPort: 80
port: 80
selector:
app: cheese
task: stilton
---
apiVersion: v1
kind: Service
metadata:
name: cheddar
spec:
ports:
- name: http
targetPort: 80
port: 80
selector:
app: cheese
task: cheddar
---
apiVersion: v1
kind: Service
metadata:
name: wensleydale
spec:
ports:
- name: http
targetPort: 80
port: 80
selector:
app: cheese
task: wensleydale

6.3. Création de l’ingress

1
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheese-ingress.yaml

Contenu du fichier distant cheese-ingress.yaml :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: cheese
spec:
rules:
- host: stilton.minikube
http:
paths:
- path: /
backend:
serviceName: stilton
servicePort: http
- host: cheddar.minikube
http:
paths:
- path: /
backend:
serviceName: cheddar
servicePort: http
- host: wensleydale.minikube
http:
paths:
- path: /
backend:
serviceName: wensleydale
servicePort: http

Accès aux 3 apps en dev

1
echo "116.203.33.4 stilton.minikube cheddar.minikube wensleydale.minikube" | sudo tee -a /etc/hosts

Ingress Nginx

Voir le tuto: https://github.com/kubernetes/ingress-nginx/blob/master/docs/deploy/index.md

Déploiement d’une app via helm

Installation du tiller

1
helm init --service-account tiller

Création d’un service account

1
2
3
4
kubectl create serviceaccount --namespace kube-system tiller
# Ajout des droits sur l'ensemble du cluster pour pouvoir déployer dans les différents namespaces
kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
kubectl patch deploy --namespace kube-system tiller-deploy -p '{"spec":{"template":{"spec":{"serviceaccount":"tiller"}}}}'

Déploiement

Pour accéder à une application il y a différente possibilités.

Accès via Nodeport

Déploiement d’un nodeport:
image

1
2
3
4
5
6
7
8
kubectl expose deployment hello-world --type=NodePort --name=example-service
kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
example-service NodePort 10.97.67.9 <none> 8080:30485/TCP 2s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 1h

curl http://<ip_cluster>:30485
Hello Kubernetes!

NodePort est une option que l’on configure directement dans le service. Kubernetes va allouer un port specifique sur chaque noeud du cluster pour accéder au service. Une requête arrivant sur n’importe quel noeud sera routée vers le service.

C’est cool car c’est très simple à mettre en place mais ce n’est pas robuste. De plus on ne peut pas savoir quel port sera alloué au service et pas garantir qu’il sera toujours le même…

Il y a d’autres solutions:

  • LoadBalancer
  • Ingress

Accès via LoadBalancer (si votre IAAS est capable de vous fournir un Load Balancer à la demande)

image

Egalement assez simple à mettre en place, il est possible de spécifier dans le YAML qu’un service est de type LoadBalancer.
Par contre, la fonctionnalité de load balancing doit être implémentée à l’extérieur par le cloud provider. Attention donc aux coûts (i.e. GKE) car à chaque fois que vous aurez à exposer un service au monde extérieur vous devrez créer un nouveau load balancer et obtenir une adresse IP.

Accès via Ingress

image

Contrairement au NodePort and au LoadBalancer, l’Ingress est une ressource complètement indépendante du service. On déclare, crée et détruit l’ingress indépendament des services.

C’est donc découplé et isolé des services que l’on veut exposer. Cela permet, entre autres, de mieux gérer les rêgles de routage.


Dans notre cas, nous allons utiliser un ingress.
On commence par déployer l’application en spécifiant ClusterIP comme serviceType:

1
helm install --set service.type=ClusterIP stable/ghost

A présent on peut créer notre ingress:

Note du 02/01/2018 - Procédure suivante à vérifier (validation d’ici peu… Si cela ne fonctionne pas, voir le contenu du fichier cheese-ingress.yaml plus haut)

1
cat ingress.yaml

Output:

1
2
3
4
5
6
7
8
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: iron-hyena-ghost
spec:
backend:
serviceName: iron-hyena-ghost
servicePort: 80

Puis on applique:

1
kubectl apply -f ingress.yaml

Etendre le cluster

Pour ajouter des workers au cluster existant:

1
2
3
hetzner-kube cluster add-worker --worker-server-type cx21 --datacenters nbg1-dc3 --name demo --nodes 1

#hetzner-kube cluster add-worker --worker-server-type cx21 --datacenters nbg1-dc3 --name <cluster-name> --nodes 1