Configurar Microk8s para usar repositorios de AWS ECR
Continuando con un post anterior de cómo probar Ansible AWX con Microk8s (en AWS EC2). Bueno, pues resulta que me creé una imagen personalizada para el contener awx_task
para instalar una serie de librerías y comandos que necesitaba para lanzar unos playbooks; el fichero Dockerfile
es similar a éste:
FROM ansible/awx_task:9.1.1
# Switch user to become root
USER 0
# Additional software
RUN cd && \
set -x && \
dnf install -y nmap-ncat htop && \
dnf clean all
# Ansible venv additional dependencies
RUN cd && \
source /var/lib/awx/venv/ansible/bin/activate && \
umask 0022 && \
pip install --upgrade pypsrp pysocks && \
deactivate
# Restore the original user
# https://github.com/ansible/awx/blob/devel/installer/roles/image_build/templates/Dockerfile.task.j2
USER 1000
Y me creé un repositorio en AWS ECR. Después generé la imagen y la subí al repositorio (siendo xxxxxxxxxxx
el ID de la cuenta de AWS):
$(aws ecr get-login --no-include-email)
docker build --force-rm --pull --no-cache -t xxxxxxxxxxx.dkr.ecr.eu-west-1.amazonaws.com/ansible/awx_task:9.1.1 .
docker push xxxxxxxxxxx.dkr.ecr.eu-west-1.amazonaws.com/ansible/awx_task:9.1.1
Tras esto modifiqué el fichero de inventario que usa el instalador de AWX para hacer referencia a la imagen que acabo de subir y crear.
kubernetes_task_image=xxxxxxxxxxx.dkr.ecr.eu-west-1.amazonaws.com/ansible/awx_task
Pero cuando el cluster de Kubernetes intenta obtener la imagen para crear el pod, se queda en estado ErrImagePull
con el mensaje:
Normal Pulling 2s (x3 over 46s) kubelet, pcjuan Pulling image "xxxxxxxxxxx.dkr.ecr.eu-west-1.amazonaws.com/ansible/awx_task:9.1.1"
Warning Failed 2s (x3 over 45s) kubelet, pcjuan Failed to pull image "xxxxxxxxxxx.dkr.ecr.eu-west-1.amazonaws.com/ansible/awx_task:9.1.1": rpc error: code = Unknown desc = failed to resolve image "xxxxxxxxxxx.dkr.ecr.eu-west-1.amazonaws.com/ansible/awx_task:9.1.1": no available registry endpoint: unexpected status code https://xxxxxxxxxxx.dkr.ecr.eu-west-1.amazonaws.com/v2/ansible/awx_task/manifests/9.1.1: 401 Unauthorized
Warning Failed 2s (x3 over 45s) kubelet, pcjuan Error: ErrImagePull
Esto se debe a que Kubernetes no tiene las credenciales necesarias para acceder al repositorio. Pero después de investigar, es fácil solucionarlo. Lo primero que tenemos que hacer es crear un cronjob
en Kubernetes (lo haremos con un crojob porque realmente lo que usa Docker es un token, que tiene caducidad, y hay que renovarlo cada cierto tiempo), para que haga login en el repositorio, y cree una credencial para obtener de forma correcta la imagen; para esto, crearemos un fichero llamado ecr-cred-refresh.yml
con el siguiente contenido:
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: ecr-cred-helper
spec:
concurrencyPolicy: Allow
schedule: 0 */6 * * *
failedJobsHistoryLimit: 1
successfulJobsHistoryLimit: 3
suspend: false
jobTemplate:
spec:
template:
spec:
containers:
- command:
- /bin/sh
- -c
- |-
NAMESPACE=awx
SERVICE_ACCOUNT=awx
ACCOUNT=$(aws sts get-caller-identity --query 'Account' --output text)
REGION=$(curl -s http://169.254.169.254/latest/dynamic/instance-identity/document | python -c "import json,sys; print(json.loads(sys.stdin.read())['region'])")
SECRET_NAME=${REGION}-ecr-registry
EMAIL=anymail.doesnt.matter@email.com
TOKEN=$(aws ecr get-login --region ${REGION} --registry-ids ${ACCOUNT} | cut -d' ' -f6)
echo "ENV variables setup done."
kubectl -n ${NAMESPACE} delete secret --ignore-not-found $SECRET_NAME
kubectl -n ${NAMESPACE} create secret docker-registry $SECRET_NAME \
--docker-server=https://${ACCOUNT}.dkr.ecr.${REGION}.amazonaws.com \
--docker-username=AWS \
--docker-password="${TOKEN}" \
--docker-email="${EMAIL}"
echo "Secret created by name $SECRET_NAME"
kubectl -n ${NAMESPACE} patch serviceaccount ${SERVICE_ACCOUNT} -p '{"imagePullSecrets":[{"name":"'$SECRET_NAME'"}]}'
echo "All done."
image: odaniait/aws-kubectl:latest
imagePullPolicy: IfNotPresent
name: ecr-cred-helper
resources: {}
securityContext:
capabilities: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: Default
hostNetwork: true
restartPolicy: Never
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
En el fichero anterior, dependiendo de nuestra configuración particular, podremos cambiar el valor de las variables NAMESPACE
y SERVICE_ACCOUNT
, y también especificar manualmente las variables ACCOUNT
y REGION
si no queremos que el script las auto-detecte porque usamos otras en concreto.
Básicamente, lo que hace esto, es crear un trabajo de cron, que lanza un contenedor y ejecuta el script en Bash definido en la especificación del pod. En resumen:
- Obtiene las credenciales de acceso a ECR utilizando la cli de AWS
- Elimina, si existe, el secreto llamado
${REGION}-ecr-registry
- Lo crea de nuevo, con el token obtenido de ECR
- Actualiza la service account de AWX indicándole que para obtener las imágenes (
imagePullSecrets
) tiene usar las credenciales del secreto recién creado
Tras esto, importamos la definición del job en Kubernetes:
kubectl -n awx apply -f ecr-cred-refresh.yml
Este job se ejecuta cada 6 horas; si queremos forzar la ejecución, podemos hacerlo con los siguientes comandos:
JOB_NAME="manual-$(date --utc +%Y%m%d-%H%M%S)"
kubectl -n awx create job --from=cronjob/ecr-cred-helper ${JOB_NAME}
kubectl -n awx wait --for=condition=complete job.batch/${JOB_NAME}
kubectl -n awx logs job.batch/${JOB_NAME}
Pero esto por sí solo no nos vale… porque ¿dónde le decimos las crendenciales para acceder a AWS (es decir, para que desde dentro del cronjob se pueda hacer aws ecr get-login
)? Es decir, el access key y el secret. Para esto, no le pasaremos una key y un secret, sino que crearemos un rol y se lo asignaremos a la instancia EC2 de AWS donde estemos ejecutando Microk8s. El rol debe tener una policy que permita a la instancia acceder al repositorio; podemos usar la policy predefinida AmazonEC2ContainerRegistryReadOnly
o crear una manualmente.
Tras crear el rol y la policy, y asignar el rol a la instancia, podemos ejecutar manualmente el cronjob
, y ejecutar de nuevo el instalador de AWX, que ya debería obtener la imagen de Docker sin problemas.
Comandos útiles:
# Ver información de la service account de awx
kubectl -n awx describe serviceaccounts awx
# Ver información de los secretos (cuándo se actualizó/obtuvo el token por última vez)
kubectl -n awx get secrets
Probar manualmente el script:
kubectl run -i --tty --rm debug --image=odaniait/aws-kubectl:latest --restart=Never -- sh
kubectl run --generator=run-pod/v1 -n awx --rm -i --tty compass-tmp --image=odaniait/aws-kubectl:latest -- sh
Referencias: