Parámetros, entrypoints y comandos en Kubernetes, ¿cómo funcionan?

Leonardo Micheloni · March 5, 2022

Si estamos desplegando en Kubernetes en probable que si algo sale mal y necesitamos hacer troubleshooting, entre las herramientas más útiles (quitando los logs) poder ejecutar un comando o “entrar” en un container puede ser muy útil. El problema es si nuestro ejecutable es el que hace que el Pod se reinicie y no podemos obtener acceso al bash del Pod, una forma de solucionar eso es sobre-escribir el comando por defecto, vamos a hablar de eso.

Docker exec, Kubectl exec

La forma de ejecutar un comando (y obtener acceso a la línea de comandos) contra un container esté o no en Kubernetes es utilizando exec (varia un poco la sintáxis entre ambos pero es igual) algo así:

docker exec -it mycontainer bash

o en Kubernetes

kubectl exec -it mypod -- bash

Y con esto obtener acceso a la línea de comandos ya que ejecutamos bash y solicitamos modo interactivo con -it

Nuestro Pod no para de reiniciarse

El problema es que muchas veces nuestra aplicación no terminar de iniciar y genera que el Pod se reinicie todo el tiempo, entonces no podemos acceder a la línea de comando, entonces lo que podemos hacer el sobre-escribir el comando que ejecuta nuestra app y así obtener acceso a la línea de comando y ejecutarla desde ahí y hacer las pruebas correspondientes

Entrypoint, CMD, args, Commands.

Acá comienza la confusión

Si creamos una imagen de algo que no tiene un comando

FROM alpine
docker build -t test .

Y creamos un contenedor

docker run -d test

el mismo se ejecuta y finaliza inmeditamente porque la idea es que sea un proceso continuo, por eso necesitamos un comando

FROM alpine
CMD ["tail", "-f", "/dev/null"]

o un Entrypoint

FROM alpine
ENTRYPOINT ["tail", "-f", "/dev/null"]

¿Pero cuál es la diferencia entre ambos?

En principio ninguna en este caso, pero si lo que queremos es dar la posiblidad de que nuestro comando reciba parámetros (es decir algo detrás del nombre del ejecutable o script, en este caso “f” “/dev/null”) ahí sí que hay diferencia:

Cómo funciona el pasaje de parámetros en Docker?

Al final todo se resume a cómo funciona en Docker ya que Kubernetes se basa en ese comportamiento. Para no ser largo con ejemplos:

Lo que se defina con CMD se sobre-escribe por completo con parámetros en la línea de comandos, entonces esto

FROM alpine
CMD ["echo", "hola"]

Si lo ejecuto imprimer “hola” y finaliza. Pero es completamente sobre-escrito si hago esto:

docker run test echo chau

Estos parámetros reemplaza a todo lo que esté definido con CMD en el Dockerfile

Si hago esto dará error

docker run test hola

porque sobre-escribe todo, y el comando que intenta ejecutar es “chau” que no es válido. Entonces CMD me permite sobre-escribir todo pero no me da la posibilidad de cambiar parte de lo que es el comando original.

¿Qué pasa si quiere pasar parámetros pero que se agregar a mi ejecutable o script?

Para esto existe Entrypoint, si pasamos parámetros en la línea de comandos se toman como argumentos del Entrypoint, es como si se agregasen, por ejemplo esto:

FROM alpine
ENTRYPOINT ["echo"]
docker run test hola

En el ejemplo anterior daría error porque no existe un comando “hola” pero este caso imprime “hola” porque se pasa como argumento a echo

Genial, pero qué pasa si queremos tener un argumento por defecto que se pueda sobre-escribir, bien, combinamos ambos.

FROM alpine
ENTRYPOINT ["echo"]
CMD ["hola"]

Viéndolo así es simple, pero también podemos sobre-escribir el entrypoint en caso que querramos hacerlo

docker run --entrypoint ls test

Y sobre-escribimos el entrypoint y el cmd, de este modo tenemos todas opciones para poder evitar que el comando por defecto del Dockerfile se ejecute para lograr tener un contenedor estable que nos permita trabajar.

Kubernetes

Y cómo se hace todo esto en Kubernetes, con dos opciones, pero lo confuso es el nombre

  • args: es equivalente a CMD en Docker
  • COMMAND: es equivalente a ENTRYPOINT en Docker

entonces, para el ejemplo anterior en un Pod quedaría así:

 apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: test
  name: test
spec:
  containers:
  - image: test
    command: ["echo"]
    args: ["hola desde pod"]
    name: test
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

Y listo, un ejemplo de cómo tener un Pod que se quede esperando para poder ingresar en él sería:

 apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: test
  name: test
spec:
  containers:
  - image: test
    command: ["tail", "-f", "/dev/null"]
    name: test
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

y ya podemos ingresar a su línea de comandos

 kubectl exec -it test -- sh

Nos leemos.

Twitter, Facebook