Istio, SDS y cert-manager
Sam
Posted on June 8, 2019
Si has llegado hasta aquí, ya sabes qué es Kubernetes. Incluso puede que hayas levantado tu propio clúster en local, usando Minikube. O hasta en una de los cientos de nubes públicas y privadas que ofrecen un servicio de Kubernetes auto gestionado. ¡Será por opciones!
En esta serie de posts vamos a ver cómo instalar/actualizar Istio para hacerlo funcionar conjuntamente con cert-manager, que nos proporcionará certificados gratuitos mediante Let's Encrypt, y ExternalDNS, que actualizará nuestros DNS cada vez que añadamos una entrada a un Gateway de Istio.
¿Qué es SDS? ¿y mTLS?
SDS son las siglas en inglés de Secret Discovery Service. Antes de Istio 1.1, las claves privadas y certificados generados por Istio eran manejados por Citadel, y montados como volúmenes en los pods sidecar de los pods. Esto tenía ciertas desventajas:
- Al renovar un certificado, Istio reinicia en caliente el sidecar de Envoy, provocando pérdidas de rendimiento.
- Las claves privadas se guardaban en secretos de Kubernetes, lo cual tiene ciertos riesgos.
Resumiendo, SDS soluciona estos problemas generando una clave privada y CSR en Citadel (que funciona como DaemonSet, es decir, en todos los nodos de Kubernetes) mediante token JWT enviado por el sidecar de Envoy. Citadel comprueba el token y, si todo es correcto, genera y envía el certificado y la clave privada al sidecar.
Podéis encontrar más detalles de cómo funciona en el blog de Istio, y en la documentación de Envoy.
Por otra parte, mTLS (mutual TLS), configura todos los pods del clúster para que hablen entre ellos usando TLS, por lo que todas las comunicaciones entre microservicios estarán cifradas y los componentes de Istio se encargarán de gestionar los certificados.
Instalando Istio con mTLS y SDS
En este artículo, partimos de un clúster vacío. Si ya tenemos un clúster funcionando sin Istio, tenemos que tener en cuenta que, para que Istio inyecte el pod sidecar en un namespace, tendremos que etiquetar ese namespace con la siguiente label:
kubectl label namespace production istio-injection=enabled
Una vez aplicado, tendremos que levantar nuevos pods para que Istio inyecte el sidecar, y eliminar los antiguos.
Para instalar Istio, usaremos Helm. Nos descargamos la última versión de Istio, y la descomprimimos.
Instalamos primeros los CRDs de Istio en el clúster. Puedes omitir este paso si ya lo tenías instalado:
helm install --name istio-init --namespace istio-system istio-1.1.7/install/kubernetes/helm/istio-init
Crearemos un fichero de values parecido a values-istio-sds-auth.yaml
que viene en el tar, con ciertas modificaciones que explico comentadas en el fichero:
$ cat values-istio-sds-auth.yaml
global:
controlPlaneSecurityEnabled: false
mtls:
enabled: true
sds:
enabled: true
udsPath: "unix:/var/run/sds/uds_path"
useNormalJwt: true
nodeagent:
enabled: true
image: node-agent-k8s
env:
CA_PROVIDER: "Citadel"
CA_ADDR: "istio-citadel:8060"
VALID_TOKEN: true
# Fin del fichero original
# Valores añadidos manualmente
# En la sección anterior, el parámetro global.sds.enabled=true activa SDS únicamente en los pods sidecar de Istio.
# En esta sección, configuramos el ingressgateway para que reciba los certificados con el mismo método.
gateways:
istio-ingressgateway:
enabled: true
sds:
enabled: true
# Esta sección inferior soluciona los livenessProbe y readinessProbe de Kubernetes configurados mediante httpGet,
# que fallan por el sidecar de Istio.
sidecarInjectorWebhook:
enabled: true
rewriteAppHTTPProbe: true
Instalamos con el comando:
helm install \
--name istio \
--namespace istio-system \
istio-1.1.7/install/kubernetes/helm/istio \
--values values.yaml # values.yaml es nuestro fichero modificado
Tras unos minutos de tensión, deberías tener Istio funcionando en tu clúster:
$ kubectl get pods -n istio-system
NAME READY STATUS RESTARTS AGE
istio-citadel-7f447d4d4b-f8lzt 1/1 Running 0 1m
istio-galley-84749d54b7-7sngg 1/1 Running 0 1m
istio-ingressgateway-57c97bb7f6-8294g 2/2 Running 0 1m
istio-nodeagent-nrl7v 1/1 Running 0 1m
istio-nodeagent-vkchb 1/1 Running 0 1m
istio-pilot-76899788b6-xcw7t 2/2 Running 0 1m
istio-policy-578bcb878f-ddwh7 2/2 Running 2 1m
istio-sidecar-injector-6895997989-5qsnn 1/1 Running 0 1m
istio-telemetry-5448cbd995-jcpdg 2/2 Running 2 1m
¡Enhorabuena! Ya tienes Istio funcionando. Tómate un café, te lo has ganado.
Instalando cert-manager
Debido a un bug con cert-manager y la última versión de Helm, vamos a instalar el primero templatizando el chart de cert-manager.
missing "caBundle" in ValidatingWebhookConfiguration when installing with helm #1702
Describe the bug:
When installing using helm as per documentation helm install --name cert-manager --namespace cert-manager --version v0.7.2 jetstack/cert-manager
(step-by-step following the documentation on a clean new cluster running v1.12.7
in cluster (AKS) and helm v2.14.0
)
The result is: Error: validation failed: error validating "": error validating data: [ValidationError(ValidatingWebhookConfiguration.webhooks[0].clientConfig): missing required field "caBundle" in io.k8s.api.admissionregistration.v1beta1.WebhookClientConfig, ValidationError(ValidatingWebhookConfiguration.webhooks[1].clientConfig): missing required field "caBundle" in io.k8s.api.admissionregistration.v1beta1.WebhookClientConfig, ValidationError(ValidatingWebhookConfiguration.webhooks[2].clientConfig): missing required field "caBundle" in io.k8s.api.admissionregistration.v1beta1.WebhookClientConfig]
Expected behaviour: Cert-manager is installed
Steps to reproduce the bug: Steps as per https://docs.cert-manager.io/en/latest/getting-started/install.html#installing-with-helm
Anything else we need to know?:
Environment details::
- Kubernetes version (e.g. v1.12.7):
- Cloud-provider/provisioner (e.g. GKE, kops AWS, etc): AKS
- cert-manager version (e.g. v0.4.0): 0.7.2
- Install method (e.g. helm or static manifests): helm
/kind bug
Descargamos el chart de cert-manager y descomprimimos:
helm fetch --untar stable/cert-manager
Como no tenemos que modificar ningún parámetro del values.yaml
para instalarlo en el clúster, templatizamos y aplicamos:
helm template . --name cert-manager --namespace istio-system > cert-manager.yaml
kubectl apply --file cert-manager.yaml
IMPORTANTE: Como veis, instalamos cert-manager en el namespace de Istio, istio-system. Esto es un requisito, ya que para que funcione SDS, los secretos que contienen los certificados deben encontrarse en el mismo namespace en el que está Istio. Los Gateways y VirtualServices de Istio podrán estar en su namespace correspondiente.
NOTA: en el propio chart de Istio podemos indicar una opción para instalar cert-manager. En este artículo lo instalamos de manera independiente para poder manejarlo independientemente de la instalación de Istio.
Una vez esté instalado cert-manager en el namespace de Istio, tendremos que configurar un emisor de certificados. Vamos a configurarlo para que use Let's Encrypt, y confirme los certificados resolviendo un DNS01 Challenge, que nos permitirá generar certificados de tipo wildcard, es decir, *.example.com
.
En este caso, usamos AWS Route 53 como DNS, por lo que el clúster o pods necesitarán unos permisos específicos de IAM para trabajar con este servicio de AWS.
Generamos el fichero clusterissuer.yaml
con este contenido:
# clusterissuer.yaml
apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
name: letsencrypt
labels:
app: certmanager
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: <EMAIL> # Sustituir por un correo válido
privateKeySecretRef:
name: letsencrypt # Secreto donde cert-manager guardará datos
# Configuramos el proveedor para Route 53. En este caso no se indican credenciales de AWS,
# dado que los pods y/o nodos de k8s tienen asignado un rol de IAM con permisos.
dns01:
providers:
- name: route53
route53:
region: eu-west-1
# # Descomenta esto, y modifica acorde,
# # para usar unas credenciales de IAM que otorguen los permisos
# accessKeyID: AKIAIOSFODNN7EXAMPLE
# secretAccessKeySecretRef:
# name: prod-route53-credentials-secret
# key: secret-access-key
Y aplicamos:
kubectl apply -f clusterissuer.yaml
Tendremos disponible, a nivel de clúster, la creación de certificados usando los certificados de Let's Encrypt. Pero primero tenemos que definir un certificado, para que cert-manager se encargue de solicitarlo automáticamente:
# wildcard.example.com.yaml
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
name: wildcard.example.com
namespace: istio-system # Recordad: es necesario que los certificados estén en el namespace de Istio
spec:
secretName: wildcard.example.com
issuerRef:
kind: ClusterIssuer
name: letsencrypt
commonName: "*.example.com"
dnsNames:
- example.com
acme:
config:
- dns01:
provider: route53
domains:
- '*.example.com'
- example.com
kubectl apply -f wildcard.example.com.yaml
Si todo está correctamente configurado, en unos minutos tendremos disponible el certificado:
$ kubectl describe certificates wildcard.example.com
[...]
Status:
Conditions:
Last Transition Time: 2019-05-30T09:27:56Z
Message: Certificate is up to date and has not expired
Reason: Ready
Status: True
Type: Ready
Not After: 2019-08-28T08:27:54Z
[...]
¡Excelente! Ya tenemos el certificado. Pero, ¿cómo hacemos para que Istio envie el tráfico a nuestros pods, y se encargue de configurar el certificado? Para ello, hacemos uso de los Gateways y VirtualServices de Istio.
Configurando Gateways y VirtualServices
Esta es fácil. Simplemente tendremos que generar un Gateway de Istio donde configuramos el DNS y el certificado, y un VirtualService que envíe el tráfico según el host al servicio correspondiente de Kubernetes. Para este ejemplo, usaremos el servicio nginx
:
# route.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: example-gateway
namespace: production # Podemos crear los Gateways en el namespace que queramos
spec:
selector:
istio: ingressgateway # Gateway de Istio por donde entra el tráfico al service mesh
servers:
# Configuramos el puerto 80 para que Istio haga la redirección a HTTPS
- port:
number: 80
name: http-redirect
protocol: HTTP
hosts:
- "nginx.example.com"
tls:
httpsRedirect: true
- port:
number: 443
name: nginx-https
protocol: HTTPS
tls:
# En credentialName, indicamos el nombre del secreto configurado
# en el Certificate anteriormente creado
credentialName: wildcard.example.com
mode: SIMPLE
# En las dos siguientes claves, indicamos a Istio que use el sistema SDS
# para obtener el certificado
privateKey: sds
serverCertificate: sds
hosts:
- "nginx.example.com"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: nginx-route
namespace: production
spec:
gateways:
- example-gateway # OJO, hace referencia al gateway creado más arriba
hosts:
- nginx.example.com
http:
- match:
- uri:
prefix: "/"
route:
- destination:
host: nginx.production.svc.cluster.local
port:
number: 443
Aplicamos este fichero con kubectl apply -f routes.yaml
, et voilà! Istio inyectará automáticamente el certificado configurado (en este caso, el wildcard) cuando entremos al DNS del servicio, nginx.example.com
.
¿Pero no íbamos a configurar ExternalDNS para que generara las entradas automáticamente en Route 53? De momento, hazlo a mano, y en el próximo post vemos cómo integrar ExternalDNS con Istio, y Route 53.
Prometo que será corto.
Fuentes y más información
Posted on June 8, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.