Quede aca: https://docs.docker.com/develop/develop-images/baseimages/
Docker se compone de 3 elementos
- Un servidor / daemon que administra todo
dockerd
- Un cliente que interactua con ese servidor y le dice que hacer
- el CLI
docker
- Docker REST API
- el CLI
- Docker objects que representan los elementos relevantes
- Imagenes (un filesystem read-only)
- Containers (un filesystem basado en una imagen, que ademas es writable y corre procesos)
- Volumes
- Networks
Ademas podes agrupar containers instanciados en diferentes daemons (osea probablemente en diferentes pcs) a esto se lo llama “service” y te permiten tener tener Computacion distribuida
- Las IMAGES son un Read-only filesystem que son el punto de partida para crear un container
- Un CONTAINER es un conjunto de procesos corriendo en un filesystem que es una copia de una imagen cuando se creo el container
Podes ver los containers como instanciaciones de una imagen, cuando creas un container estas copando el filesystem de una imagen y colocandolo en un read/write filesystem que ademas tiene funcionando ciertos procesos (el container).
El sistema docker funciona con layers que consisten en
- Una serie de imagenes, cada una sobreescribiendo y añadiendo cosas de la anterior generando un filesystem final
- Ej una añade el SO, otra añade apache, otra añade wordpress
- Un contanier que copia el filesistem final en un filesystem read/write y lo corre con ciertos procesos
docker info
Muestra el estado del servicio docker (contenedores corriendo, pausados, detenidos, imagenes, version)docker ps <-param>
Lista los containers que estan en funcionamiento- -a Muestra containers que fueron iniciados y ahora estan detenidos
- -q Solo muestra los containers id
docker run <-param> <imagen> <comando>
Crea un container desde una imagen, si no la encuentra la descarga del repositorio- -param
- –name nombre Le asigna un nombre al container que podes usar en lugar de su ID
- -it Hace que el container sea interactivo (te conecta a su consola)
- -d run in background (daemon)
-p <hostport>:<containerport>
mapea un puerto a otro
- imagen Nombre de la imagen desde la que se crea el container
- comando El comando que correr apenas el container termine de crearse. overridea el comando default del dockerfile (CMD)
- -param
docker image ls
Lista las imagenes disponibles en el OS para crear containersdocker container ls
Lista containersdocker container ls --all
muestra containers funcionando
docker logs <id>
Muestra el log de stdout de un containerdocker container stop <id>
Detiene el contenedor gracefullydocker container kill <id>
detiene el contenedor no gracefullydocker pull <image name>:<TAG>
descarga de dockerhub una imagen, las tags indican variaciones de una imagendocker diff <id>
muestra las diferencias entre el container y su base imagedocker commit <id>
crea una imagen del containerdocker tag <imageId> <tuTag>
coloca un nombre (tag) a una imagendocker rm <id>
Elimina un containerdocker rmi <idImagen>
Elimina una imagendocker port <id>
muestra el mapping de puertos para un contianer
Para evitar el uso de SUDO es critico agregar al usuario que va a administrar docker al grupo docker
que se crea en el sistema al instalarlo.
En windows no podes usar localhost para comunicarte con un container usando un puerto mapeado, tenes que usar la ip del container y el puerto mapeado, la obtenes con el comando docker-machine ip
En windows CTRL-C
no detiene el contenedor, tenes que detenerlo con docker container stop <Container NAME or ID
Una buena analogia de los containers es que son como objetos instanciados de una clase (la imagen). son iguales a la imagen al momento de crearse, pero luego a lo largo de su vida son modificados.
Para instanciar un container desde una imagen usas.
Si no tenes la imagen cargada entonces docker intenta bajarla de dockerhub
docker run <-param> <imagen> <comando>
- -param
- - -name nombre Le asigna un nombre al container que podes usar en lugar de su ID
- -it Hace que el container sea interactivo (te conecta a su consola)
- -d run in background (daemon)
-p <hostport>:<containerport>
mapea un puerto a otro
- imagen Nombre de la imagen desde la que se crea el container
- comando El comando que correr apenas el container termine de crearse. overridea el comando default del dockerfile (CMD)
Los containers y las imagenes siempre tienen ids que se ven como un hash, pero ademas pueden tener:
- nombres en el caso de los containers
- tags en el caso de las imagenes
Donde sea que puedas usar un ID podes usar el nombre o el tag
Para nombrarlos usamos el parametro --name
docker run --name miNombre <imagen>
Podes controlar los containers que tenes usando
docker container stop <id>
- Detiene el proceso
docker container start <id>
docker container pause<id>
El stdout de un container va a los logs de docker, accesibles desde
docker logs <id>
En produccion vas a querer mantener los containers funcionando como daemons usando docker run -d <imagen>
Simplemente ejecutas un bash en el container y pedis que sea interactivo
docker exec -t -i container_name /bin/bash
- Los containers deben ser stateless y deben poder ser detenidos, reiniciados o reinstanciados de su imagen en todo momento
- Cada container debe tener un unico concern
CRITICO: Los containers son efimeros, si el server se apaga su estado se pierde.
Para crear una imagen
- Creas un container en base a una o varias imagenes
- haces tus cambios
- corres
docker commit <idContainer> <tag>
y docker guarda una nueva imagen basada en tu container (usando elid
) con el nombre que coloques enTAG
Para crear una imagen de docker de forma programatica usamos un dockerfile junto con archivos que querramos copiar al contenedor.
Los necesitas a todos en la misma carpeta y a esta la llamaremos build context.
Cada comando instancia un container copiando el container del comando anterior. el container del comando anterior es borrado.
FROM <imagen base>
RUN <comandos ej: apt-get update>
RUN <comandos ej: apt-get install -y wget>
Tips: Conviene siempre colocar los comandos de tal forma que las cosas con menos cambios vayan arriba y las de mayor frecuencia de cambios vayan abajo. Esto permite aprovechar el build cache para hacer los builds mas rapidos.
En tu dockerfile probablemente quieras:
- usar una imagen oficial de base usando
FROM
- Copiar archivos al contenedor usando
COPY
- instalar dependencias con algun package manager
RUN
usando algun package.json que copiaste usando copy- apt-get
- npm
- pip
- Exponer puertos usando
EXPOSE
- Setear variables de entorno
- usando
ENV
no recomendado - copiando archivos .sh a /etc/profile.d
- usando
# Use an official Python runtime as a parent image FROM python:2.7-slim
# Set the working directory to /app WORKDIR /app
# Copias cosas del directorio del dockerfile al contenedor COPY . /app
# Install any needed packages specified in requirements.txt RUN pip install --trusted-host pypi.python.org -r requirements.txt
# Make port 80 available to the world outside this container EXPOSE 80
# Define environment variable ENV NAME World
# Run app.py when the container launches CMD ["python", "app.py"]
Finalmente usas el dockerfile asi:
Para crear tu imagen entonces haces lo siguiente
Docker build --tag <miTag>:<version> <pathHaciaElBuildContext>
Una vez que tenes un Dockerfile podes crear tu imagen. Docker build
necesita dos cosas para crear una imagen
- EL DockerFile que describe que hacer
- Un Build context que es una carpeta con:
- El dockerFile
- Todos los archivos que podrias necesitar copiar al container con comandos como
COPY
,ADD
, etc
EL build context agranda la imagen (no asi el container) ya que se copia todo el build context para tenerlo disponible a futuros layers que pudieran querer correr comandos como
COPY
. es por eso que te conviene mantenerlo chiquito
Para eso podes usar el archivo.dockerignore
para ignorar los archivos que no queres que formen parte del build context
Para crear tu container entonces haces lo siguiente
Docker build --tag <miTag>:<version> <pathHaciaElBuildContext>
Una vez creado, podes ver el historial de containers intermedios y comandos corridos con
docker history <id imagen>
Docker guarda un cache de las operaciones para hacer los build processes mas rapidos.
El cache funciona de la siguiente manera:
- Si la base image es la misma, uso cache
- linea por linea del dockerfile voy usando los layers en el cache a medida que veo que las instrucciones fueron las mismas
- Para las instrucciones
COPY
yADD
hago un checksum de los archivos, si los checksums son iguales a los del cache entonces lo uso
- Para las instrucciones
NOTA IMPORTANTE SOBRE APT-GET: Notese que esto se cachea
RUN apt-get update RUN apt-get install -y nginx
Por lo tanto cambiar el
apt-get install
no genera que se vuelva a correr elapt-get update
. Te conviene entonces poner todo junto en un comandoRUN apt-get update && apt-get install -y \ aufs-tools \ automake \ ETC ETC...
FROM:<imagen>
Indica la imagen de baseLABEL:<key>=<value>
el label para la imagen, algo human friendly, podes tener varios labels.key
deberia ser prefixeado por un dominio que controles (Best practice), puede tener puntos o guionesValue
puede ser cualquier cosa (dominio, etc)
WORKDIR:<PATH>
establece el woring directory del container desde este punto en adelante (ejRUN
oCMD
arrancan con ese path)USER: <username|UID>
indica con que usuario se correra el servicio, es para no correr el servicio como root.- Precio a esto necesitas crear el usuario, ej:
RUN useradd --no-log-init -r -g postgres postgres
- Precio a esto necesitas crear el usuario, ej:
CMD <comando>
indica un comando default que correr al iniciar el container (de bash) .- Si se corre un comando al iniciar el container entonces overrideas al
CMD
. ej:docker run <-param> <imagen> <comando>
- solo puede haber un CMD command en todo el docker file, si hay mas de uno entonces se corre el ultimo.
- Si se corre un comando al iniciar el container entonces overrideas al
ENTRYPOING ["<command>","<option1>","<option2>"...]
- Es como
CMD
- no se overridea al insertar un comando con
docker run
ENTRYPOING ["wget","-o","-q"]
- Lo que se coloque como comando en
docker run <-param> <imagen> <comando>
se appendea al final del comando seteado en entrypoint
- Es como
EXPOSE <portNumber>
expone un puerto del container al host.ADD <path host> <path container>
Permite copiar un archivo/directorio del host al containerCOPY <path host> <path container>
Igual que addENV NAME World
define variables de entornoWORKDIR <pathEnContainer>
setea el working directory
Los contenedores de docker normalmente son cerrados y no pueden escuchar con ningun puerto salvo que lo expongas usando EXPOSE
.
- EXPONER: expone un puerto del contenedor para que el contenedor pueda escucharlo, solo otros contenedores pueden comunicarse con el puerto, no es accesible fuera del host.
- MAPEAR: Mapea un puerto expuesto a un puerto del host. De esta manera el contenedor puede comunicarse con el mundo exterior usando la ip y un puerto del host.
Normalmente los containers pueden comunicarse con el mundo exterior usando la ip del host, pero el mundo exterior no puede comunicarse con ellos (ya que los paquetes los recibe el host).
Para solucionar esto usamos port mapping, que es tomar un puerto del host y mapearlo a un puerto del container.
POR EJEMPLO: podemos mapear el puerto 555555 del host al puerto 80 del container, para que los paquetes que lleguen al puerto 555555 del host sean redirigidos al container como si vinieran de su puerto 80
Para realizar el mapeo usamos
- el flag
--publish-all
para autodetectar los puertos activos del container y mapearlos automaticamente a puertos random del hostdocker run <-param> --publish-all <imagen> <comando>
- el flag
-p <puerto container>:<puerto host>
Ahora podemos ver los puertos mapeados por docker para ese container usando docker port <id>
.
Podes comunicar containers simplemente indicando
docker run <-param> --link <containerId>:<aliasEnNuevoContainer> <imagen> <comando>
Ahora si miras el host file de este container vas a encontrar una enrutacion con el host name aliasEnNuevoContainer
y la IP interna del container containerId
CRITICO: Es importante tener en cuenta que los volumenes compartidos no se commitean como parte de una imagen
Para montar un directorio del host como un volumen en un container podes hacer:
docker run <-param> -v path/en/el/container:path/en/el/host<imagen> <comando>
- Podes agregar permisos
docker run <-param> -v path/en/el/container:path/en/el/host:rw- <imagen> <comando>
Esto te permite tener archivos pemanentes que no se eliminan si el container se detiene.
Podes compartir los volumenes de un container con otros, incluso si este no esta corriendo.
Para crear un volumen compartible usas el siguiente comando
docker run <-param> -v path/en/el/container <nombreDeVolumen> <imagen> <comando>
Para usar los volumenes de otro container
docker run <-param> -volumes-from <containerId> <imagen> <comando>
Docker compose te permite instanciar y configurar multiples contenedores desde un solo archivo denominado dockerCompose en el formato .yml
En lugar de ejecutar docker run <-param> -volumes-from <containerId> <imagen> <comando>
manualmente para cada container declaras
version: 2 #Version de sintaxis de dockercompose #Declaras una red de load balancers (opcional) networks: nombreDeLoadBalancer: #Declaras servicios services: # Cada servicio es un contenedor, en este caso #"web" y "database" web: name: "nombre del contenedor" #como --name image: "miwilfly:7" #imagen usada ports: #el mapeo de puertos - "80:8000" - "21:300" links: ·#aliases - basedatos:miAliasVisibleParaWeb volumes: - "<path en container>:<pathHost>" deploy: placement: #ej Solo correr en swarm manager constraints: [node.role == manager] command: <comando, equivalente a CMD> networks: - nombreDeLoadBalancer basedatos: image:"mysql:lastest" ports: - "3306:3306"
Dockerhub funciona como github pero para containers.
- Push - podes publicar tus imagenes tagueandolas con el nombre de usuario de dockerhub, tu repositiorio y un tag para identificarlo (una version posiblemente)
docker tag <imagen> username/repository:tag docker push username/repository:tag
- Pull - Podes tomar imagenes tuyas o de otro repositorio haciando un pull con la sintaxis
username/repository:tag
docker run -p 4000:80 username/repository:tag
Distributed systems se basa principalmente en el concepto de services o de micro-services, es decir dividir el sistema en varias partes que sea stateless y corran en diferentes servidores. cada servicio va en uno o varios containers funcionando en paralelo
Nomenclatura:
- SWARM WORKER nodo con un docker daemon que corre algunos o todos los tasks de uno o varios services. se comunica mediante DOCKER API
- SWARM MANAGER computadora que dirige el swarm y le dice a cada node que task de cada servicio instanciar. se comunica mediante DOCKER API
- SWARM: conjunto de nodes.
- NODE: computadora que esta corriendo un docker daemon
- STACK: Conjunto de services. configurado en dockerCompose
- SERVICE: La descripcion de cuantos tasks de una misma imagen se deben mantener instanciados a lo largo del swarm en todo momento (ej 10 apache servers). configurado en dockerCompose.
- TASK: un container
- LOAD-BALANCER: El sistema que se encarga de distribuir los requests entre ciertos containers
Necesitas que cada host tenga abiertos los puertos
- Port 7946 TCP/UDP for container network discovery.
- Port 4789 UDP for the container ingress network.
- Para transformar este nodo en un swarm manager hacemos
docker swarm init
- El comando devolvera un comando
docker join --token <token> <ip>
que podemos usar para unir workers al swarm
- Para ver los nodos hacemos
docker node ls
- para apagar docker swarm haces
docker swarm leave --force
Una vez creado el swarm todos tus comandos de docker los vas a realizar en el swarm manager y este se encargara de coordinar los workers
DockerCompose te permite configurar un stack indicando servicios que se componen de una cierta cantidad de contenedores instanciados de la misma imagen de ciertas caracteristicas y con ciertas limitaciones
version: "3" ##Listado de los servcios: services: ##Servicio 1, llamado "web" y su descripcion web: image: username/repo:tag deploy: replicas: 5 #Correr 5 contenedores resources: limits: cpus: "0.1" # cada uno usa max 10% de 1 CPU core memory: 50M # cada uno usa max 50mb ram restart_policy: condition: on-failure # Reiniciar si hay error ports: #Mapeo de puertos - "4000:80" networks: #Web va a usar el load-balancer "webnet" - webnet #Aca colocarias otro servico otroServicio: networks: #Defino el load-balancer network llamado "webnet" webnet:
- Para desplegar un stack en el swarm hacemos
docker stack deploy -c docker-compose.yml <nombreDelSwarm>
- Para eliminar el stack haces
docker stack rm getstartedlab
Podes ver los servicios corriendo en un stack asi
docker stack services <nombreDelSwarm>
Para ver los tasks corriendo en un service, haces
docker service ps getstartedlab_web
Para ver los tasks corriendo en un stack
docker stack ps <stackname>
Para ver los nodos hacemos
docker node ls
Podes escalar el servicio cambiando la cantidad de replicas en el docker-compose y corriendo:
docker stack deploy -c docker-compose.yml <nombreStack>
No hay necesidad de hacer nada mas, los contenedores se distribuiran en todo el swarm y el load balancer va a distribuir la carga equitativamente.
Podes acceder con la ip de cualquier worker o el swarm manager, el load balancer se va a hacer cargo de todo
Si necesitas compartir archivos entre los containers (ej archivos subidos por suers) tenes que tener un container en un solo nodo que tenga un volumen compartirdo con ese nodo
Ejemplo de docker-composer
redis:
image: redis
ports:
- "6379:6379"
volumes:
- "/home/docker/data:/data"
deploy: ##Solo existira en el swarm manager
placement:
constraints: [node.role == manager]