Previously On … Kapelal !
- Un projet GCP sous terraform avec un Kubernetes managé (GKE).
- Un ingress controller (nginx).
- Un déploiement Hugo sous k8s synchronisé avec un dépôt git (le contenu du blog).
Maintenant je m’attaque à la partie https du blog.
On a simplement besoin d’un certificat TLS. Comme pour toutes les autres actions, je ne souhaite pas avoir des actions manuelles. Pour cela je vais utiliser cert manager.
Cert manager
Cert manager est une solution qui administre la vie de certificat TLS avec Letsencrypt sous k8s.
Je vais parler d’objet k8s qui s’appelle les Custom Resources Definitions (CRD). Un pod, service, ingress, secret etc. sont les ressources “core” de k8s. Avec les CRD, on peut ajouter d’autres objets qui auront d’autres rôles.
La déclaration d’une CRD est simple
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: issuers.certmanager.k8s.io
spec:
group: certmanager.k8s.io
names:
kind: Issuer
listKind: IssuerList
plural: issuers
singular: issuer
scope: Namespaced
version: v1alpha1
Comme pour l’ingress controller avec l’ingress, la CRD a aussi besoin de son controller. D’un mécanisme qui va venir absorber la ressource pour en faire quelque chose.
La solution s’appuie sur 2 CRD pour décrire:
- Issuer: comment on discute avec Letsencrypt.
- Certificate: quel est la forme des certificats TLS.
Une chart cert manager existe déjà et on va la prendre.
Issuer
La doc
apiVersion: certmanager.k8s.io/v1alpha1
kind: Issuer
metadata:
name: "letsencrypt-prod-RELEASE-NAME"
labels:
app: RELEASE-NAME-cert-impl
chart: "cert-impl-0.1.0"
release: "RELEASE-NAME"
heritage: "Tiller"
spec:
acme:
server: "https://acme-v02.api.letsencrypt.org/directory"
email: "foo@bar.com"
privateKeySecretRef:
name: "letsencrypt-prod-private-key"
dns01:
providers:
- name: clouddns
clouddns:
project: "kapelal-203808"
serviceAccountSecretRef:
name: "cloud-sa"
key: "credentials.json"
Pour simplifier, on déclare quel est:
- l’URL, l’autorité de certification, qui va nous délivrer un certificat.
- le type de challenge que l’autorité de certification va utiliser, afin de vérifier que je suis bien propriétaire du nom de domaine que je souhaite sécuriser.
Moi j’ai choisi d’utiliser le challenge DNS. Il y a aussi le challenge HTTP, voir la doc.
Pour le challenge DNS, cert manager a besoin de pouvoir créer une entrée DNS. C’est pas magique, il faut lui filer un service account GCP qui en donne le droit.
Pas de clic clic, on le fait avec terraform
# https://github.com/kapelal/terraform/tree/master/service_account
resource "google_service_account" "dns-admin" {
account_id = "dns-admin"
display_name = "dns-admin"
project = "${var.project}"
}
resource "google_service_account_key" "dns-admin-key" {
service_account_id = "${google_service_account.dns-admin.name}"
public_key_type = "TYPE_X509_PEM_FILE"
}
resource "google_project_iam_member" "service-accounts" {
role = "roles/dns.admin"
member = "serviceAccount:${google_service_account.dns-admin.email}"
}
Ce qu’on souhaite aussi, c’est de stocker le service account dans le cluster k8s pour cert-manager (avec les mêmes informations que serviceAccountSecretRef
).
Terraform sait le faire aussi
# https://github.com/kapelal/terraform/tree/master/service_account
provider "kubernetes" {
}
resource "kubernetes_secret" "google-application-credentials" {
metadata {
name = "cloud-sa"
namespace = "reverse-proxy"
}
data {
credentials.json = "${base64decode(google_service_account_key.dns-admin-key.private_key)}"
}
}
Pour configurer le provider kubernetes, il faut avoir un
kubectl
avec une conf valide sur la machine qui lance terraform. J’utilise docker pour me simplifier la tâche https://github.com/kapelal/terraform/blob/master/service_account/Makefile
Un conseil, il y a 2 autorités de certification avec Letsencrypt, une staging et une prod https://letsencrypt.org/docs/rate-limits/. Commencer par la staging !
Certificate
La doc
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
name: tls.kapelal.io-tls
spec:
acme:
config:
- dns01:
provider: clouddns
domains:
- kapelal.io
dnsNames:
- kapelal.io
issuerRef:
kind: Issuer
name: letsencrypt-prod-tls
secretName: tls.kapelal.io
Pour simplifier, on déclare quel est:
- le issuer et le challenge utilisés
- le nom du secret k8s où sera stocké le certificat TLS
- le nom de domaine a protégé
Quand on déploie le certificate, un coup de kubectl describe certificates NOM_DU_CERTIFICATE
afin de vérifier si le challenge a réussi.
Chart Helm
Les values que j’utilise pour cert manager
Pour créer le certificate et l’issuer, j’utilise cette chart avec ces values
Hugo x TLS
L’ajout du certificat TLS, avec nginx, sous k8s, se fait coté ingress
# https://github.com/kapelal/helm/blob/master/hugo/templates/ingress.yaml
spec:
rules:
- host: kapelal.io
http:
paths:
- backend:
serviceName: blog-hugo
servicePort: 443
path: /
tls:
- hosts:
- kapelal.io
secretName: tls.kapelal.io
Mais j’ai déployé mon certificate dans un namespace différent que celui de Hugo. Parce que demain, il n’y a pas que hugo qui aura besoin du certificat. Il faut le copier dans son namespace.
Pas de clic clic ou de commande kubectl
dans un Readme. Mais plutôt un controller k8s qui permet de copier une liste de secret dans tous les namespaces. Je le veux pas en one shot mais qu’a chaque nouveau namespace, cette liste soit copiée.
Comme je suis devenu récemment fan d’Elixir, avec une lib qui tape les API k8s en elixir, j’ai dev un controller k8s: https://github.com/rodesousa/replicator
Un jour, un article dessus, mais là ce n’est pas le sujet
Quelques changements dans la chart Hugo et ses values pour basculer en https
Fin~
La suite
Les prochains posts:
- Helm Chart Repository
- Metrics avec Prometheus
- Log avec Kibana/Fluent
- Vault