Conhecendo O Kubernetes - Clusters De Containers

| Comments

O que é Kubernetes

Kubernetes é uma solução Open Source desenvolvida pelo Google, originalmente chamada de K8s, como uma ferramenta para gerenciar clusters de containers (ou containeres, como prefira). Em 2005, quando a ferramenta foi desenvolvida, originalmente para uso interno, o Google doou o código à recém fundada Cloud Native Computing Foundation, em parceria com a The Linux Foundation.

O motivo do leme em sua logomarca é devido à origem grega da palavra, que vem de Kuvernetes, que representa a pessoa que pilota o navio, timoneiro.

Como objetivo primário o Kubernetes provê uma plataforma de automação para deployments, escalonamento e operações de containers de aplicações em um cluster de hosts ou nodes.

Antes de seguir com a explicação, instalação e configuração do Kubernetes, estou supondo que você já possui algum conhecimento básico sobre o que sejam containers e tenha alguma familiaridade com o Docker. Caso não possua um entendimento básico sobre containers e Docker, sugiro que leia algo antes de seguir com este artigo. Possuo um post introdutório sobre containers com um exemplo básico e prático sobre como criar containers com Docker, bem como iniciar uma simples aplicação web – aqui.

O Kubernetes é formado por uma série de componentes ou blocos que, quando utilizados coletivamente, fornecem um método de deployment, manutenção e escalonamento de clusters de aplicações baseadas em containers. Estes componentes, ou primitives como o Kubernetes os chama, foram desenvolvidos com o intuito de serem independentes, de forma que quase não se faz necessário ter conhecimento entre si para que possam funcionar e trabalhar juntos, visto que todos se comunicam e interligam através de uma API, sejam componentes internos do Kubernetes ou mesmo extensões e containers.

Embora tenha sido inicialmente desenvolvido para o deployment e utilização de bilhões de containers internamente no Google, desde que seu código passou a ser distribuído abertamente com a licença Apache Commons o Kubernetes tem sido adotado formalmente por praticamente todos os grandes provedores de serviços em nuvem.

Arquitetura do Kubernetes

Dentre os principais componentes do Kubernetes, vamos destacar os seguintes:

  • Master ou Master Controller – Host que será o gerenciador principal do Kubernetes, responsável por gerenciar os Minions ou Nodes do Cluster;

  • Nodes ou Minions – Embora normalmente a nomenclatura em diversos serviços de tecnolocia seja Node, o Kubernetes prefere chamar de Minions os hosts que fazem parte de um Cluster gerenciado pelo próprio Kubernetes. Este minion pode ser um servidor físico ou virtual, necessitando possuir um serviço de gerenciamento de containers, como o Docker, por exemplo;

  • ETCD – Embora este seja um serviço independente, estou listando-o aqui pois este será fundamental em seu ciclo de desenvolvimento com o Kubernetes. Cada Minion deverá rodar o ETCD (serviço de comunicação e gerenciamento de configurações no formato par de Chave/Valor). O ETCD é utilizado para troca e armazenamento de informações sobre os containers, pods, minions, etc.

  • Pods – São grupos de containers (um ou mais) rodando em um único minion do cluster. Cada Pod receberá um endereço IP único no Cluster como forma de possibilitar a utilização de portas sem a necessidade de se preocupar com conflitos;

  • Labels – São informações de identificação na configuração e gerenciamento dos objetos (como Pods ou Minions) formados de pares “chave:valor”;

  • Controllers – Além do Master Controller, dependendo do tamanho de sua infraestrutura e quantidade de Pods e Minions, você pode optar por ter mais de um Controller para dividir a carga e tarefas de gerenciamento. Os Controllers gerenciam um grupo de pods e, dependendo do estado de configuração desejada, podem acionar outros Controllers para lidar com as replicações e escalonamento. Os Controllers também são responsáveis pela substituiçao de Pods, caso um entre em estado de falha.

Instalação

Vamos ao que interessa…

Novamente estou supondo que você já possui alguma familiaridade com Containers, Docker e, por consequência, com GNU/Linux.

Eestarei utilizando 4 servidores virtuais rodando CentOS 7 nos exemplos a seguir, mas fica a seu critério decidir quantos utilizar.

Certamente você optar por utilizar outra distribuição, seja Debian, Ubuntu, etc.. Uma vez que optei pelo CentOS 7, estarei utilizando comandos voltados para esta distro, mas sinta-se livre para adaptar seus comandos, como substituir o “yum” pelo “apt-get”, “pacman”, etc..

Em minha configuração chamarei os servidores da seguinte forma:

  • centos-master
  • centos-minion1
  • centos-minion2
  • centos-minion3

A primeira coisa que se deve fazer sempre que se pensa em trabalhar com clusters, independente de ser um cluster de containers ou não, é ter a certeza de que os servidores terão uma correta sincronização de relógios entre si. A forma mais simples e eficiente no nosso contexto é com a utilização do NTP, portanto comece instalando o NTP nos 4 servidores, bem como habilitando o serviço e iniciando-o:

1
# yum install -y ntp
1
# systemctl enable ntpd && systemctl start ntpd

Caso queira certificar-se de que o serviço está realmente rodando:

1
2
3
4
5
6
7
8
9
10
# systemctl status ntpd

● ntpd.service - Network Time Service
   Loaded: loaded (/usr/lib/systemd/system/ntpd.service; enabled; vendor preset: disabled)
   Active: active (running) since Sat 2017-06-24 17:46:02 UTC; 3s ago
  Process: 1586 ExecStart=/usr/sbin/ntpd -u ntp:ntp $OPTIONS (code=exited, status=0/SUCCESS)
 Main PID: 1587 (ntpd)
   Memory: 2.1M
   CGroup: /system.slice/ntpd.service
           └─1587 /usr/sbin/ntpd -u ntp:ntp -g

Pinga?

É importante nos certificarmos de que os servidores conseguem se comunicar e de que conseguem resolver nomes corretamente.

Neste exemplo, conforme informado mais acima, estamos utilizando 4 servidores com os seguintes nomes: centos-master, centos-minion1, centos-minion2 e centos-minion3, portanto vamos editar o arquivo /etc/hosts de cada um deles para que possam se comunicar pelos nomes que desejamos:

Insira as seguintes linhas no arquivo /etc/hosts dos 4 servidores:

Lembre-se de substituir os IPs pelos IPs dos servidores em seu ambiente

1
2
3
4
5
6
7
8
9
10
11
# Ip local do servidor master
172.31.22.126   centos-master

# Ip local do minion1
172.31.120.16   centos-minion1

# Ip local do minion2
172.31.25.6     centos-minion2

# Ip local do minion3
172.31.123.22   centos-minion3

Feito isto, tente pingar do master para os 3 minions utilizando os nomes especificados no /etc/hosts:

1
2
3
4
5
6
7
8
9
10
11
[root@kalib1 ~]# ping centos-minion1
PING centos-minion1 (172.31.120.16) 56(84) bytes of data.
64 bytes from centos-minion1 (172.31.120.16): icmp_seq=1 ttl=64 time=1.06 ms

[root@kalib1 ~]# ping centos-minion2
PING centos-minion2 (172.31.25.6) 56(84) bytes of data.
64 bytes from centos-minion2 (172.31.25.6): icmp_seq=1 ttl=64 time=0.588 ms

[root@kalib1 ~]# ping centos-minion3
PING centos-minion3 (172.31.123.22) 56(84) bytes of data.
64 bytes from centos-minion3 (172.31.123.22): icmp_seq=1 ttl=64 time=1.24 ms

Você pode realizar o mesmo teste a partir dos minions, pingando entre si e também para o centos-master.

Uma vez que tenhamos certeza de que todos os hosts se comunicam, é hora de instalar mais alguns pacotes necessários.

Primeiramente, vamos configurar o repositório do Docker para o CentOS 7:

Vamos criar o seguinte arquivo de repositório:

1
# vim /etc/yum.repos.d/virt7-docker-common-release.repos

O conteúdo deste arquivo será o seguinte:

1
2
3
4
[virt7-docker-common-release]
name=virt7-docker-common-release
baseurl=http://cbs.centos.org/repos/virt7-docker-common-release/x86_64/os/
gpgcheck=0

Este arquivo deverá ser criado nos 4 servidores.

Em seguida, vamos atualizar a nossa base de repositórios e pacotes, também nos 4 servidores, bem como habilitar o novo repositório para instalar os pacotes docker e kubernetes:

1
# yum update
1
# yum install -y --enablerepo=virt7-docker-common-release docker kubernetes

Como dito na introdução, também precisaremos do etcd para o armazenamento e troca de configurações, portanto vamos instalá-lo também nos 4 hosts:

1
# yum instal -y etcd

Configuração

Vamos começar com a configuração básica dos serviços envolvidos. Primeiramente, vamos abrir o arquivo de configuração do kubernetes e fazer algumas alterações:

/etc/kubernetes/config

No arquivo config altere as seguintes linhas:

Edite o valor do parâmetro KUBE_MASTER, de forma que nosso master possa ser encontrado pelo nome que definimos no hosts file. O valor original é “—master=http://127.0.0.1:8080”, portanto mudaremos para o seguinte:

1
KUBE_MASTER="--master=http://centos-master:8080"

Ainda neste arquivo de configuração, vamos inserir a configuração do serviço ETCD, portanto inclua a seguinte linha ao final do arquivo:

1
KUBE_ETCD_SERVERS="--etcd-servers=http://centos-master:2379"

Seu arquivo de configuração deverá estar similar a este:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
###
# kubernetes system config
#
# The following values are used to configure various aspects of all
# kubernetes services, including
#
#   kube-apiserver.service
#   kube-controller-manager.service
#   kube-scheduler.service
#   kubelet.service
#   kube-proxy.service
# logging to stderr means we get it in the systemd journal
KUBE_LOGTOSTDERR="--logtostderr=true"

# journal message level, 0 is debug
KUBE_LOG_LEVEL="--v=0"

# Should this cluster be allowed to run privileged docker containers
KUBE_ALLOW_PRIV="--allow-privileged=false"

# How the controller-manager, scheduler, and proxy find the apiserver
KUBE_MASTER="--master=http://centos-master:8080"

KUBE_ETCD_SERVERS="--etcd-servers=http://centos-master:2379"

Repita esta mesma configuração nos 4 hosts. Todos eles devem utilizar exatamente os mesmos valores utilizados aqui, apontando KUBE_MASTER e KUBE_ETCD_SERVERS para centos-master, visto que este será o responsável por gerenciar todos os nossos minions.

Uma vez que o arquivo de configuração do kubernetes esteja pronto nos 4 hosts, vamos configurar o serviço de API do kubernetes:

/etc/kubernetes/apiserver

Esta configuração abaixo será apenas para o Master.

Edite o valor do parâmetro KUBE_API_ADDRESS, que originalmente é “—insecure-bind-address=127.0.0.1”, de forma que possamos novamente receber comunicação dos demais hosts.

1
KUBE_API_ADDRESS="--address=0.0.0.0"

Descomente as linhas KUBE_API_PORT e KUBELET_PORT, para que possamos estabelecer as portas de comunicação com a API:

1
2
3
4
5
# The port on the local server to listen on.
KUBE_API_PORT="--port=8080"

# Port minions listen on
KUBELET_PORT="--kubelet-port=10250"

Em nosso exemplo não utilizaremos o parâmetro KUBE_ADMISSION_CONTROL, o qual nos permite ter mais controles e restrições sobre quais nodes ou minios podem entrar em nosso ambiente, portanto vamos apenas comentar esta linha por enquanto:

1
2
# default admission control policies
# KUBE_ADMISSION_CONTROL="--admission-control=NamespaceLifecycle,NamespaceExists,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota"

Nosso arquivo /etc/kubernetes/apiserver deverá estar assim:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
##
# kubernetes system config
#
# The following values are used to configure the kube-apiserver
#

# The address on the local server to listen to.
KUBE_API_ADDRESS="--address=0.0.0.0"

# The port on the local server to listen on.
KUBE_API_PORT="--port=8080"

# Port minions listen on
KUBELET_PORT="--kubelet-port=10250"

# Comma separated list of nodes in the etcd cluster
KUBE_ETCD_SERVERS="--etcd-servers=http://127.0.0.1:2379"

# Address range to use for services
KUBE_SERVICE_ADDRESSES="--service-cluster-ip-range=10.254.0.0/16"

# default admission control policies
# KUBE_ADMISSION_CONTROL="--admission-control=NamespaceLifecycle,NamespaceExists,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota"

# Add your own!
KUBE_API_ARGS=""

Salve e feche o arquivo. Novamente, esta configuração deve ser feita apenas para o Master.

Agora vamos configurar o serviço ETCD:

/etc/etcd/etcd.conf

Esta configuração abaixo será apenas para o Master.

Edite os valores dos parâmetros ETCD_LISTEN_CLIENT_URLS e ETCD_ADVERTISE_CLIENT_URLS, que originalmente apontam para localhost. Como desejamos que nosso etcd escute requisições dos demais hosts, altere para o seguinte:

1
2
3
4
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"
...
...
ETCD_ADVERTISE_CLIENT_URLS="http://0.0.0.0:2379"

Novamente, não é necessário alterar a configuração do etcd nos demais hosts, apenas no Master.

Uma vez que as configurações iniciais foram feitas, vamos habilitar e iniciar os serviços necessários no Master, sendo eles:

  • etcd
  • kube-apiserver
  • kube-controller-manager
  • kube-scheduler
1
# systemctl enable etcd kube-apiserver kube-controller-manager kube-scheduler
1
# systemctl start etcd kube-apiserver kube-controller-manager kube-scheduler

Os 4 serviços devem estar rodando. Para termos certeza, vamos checar o status dos mesmos:

1
2
3
4
5
# systemctl status etcd kube-apiserver kube-controller-manager kube-scheduler | grep "(running)"
   Active: active (running) since Sat 2017-06-24 21:45:37 UTC; 1min ago
   Active: active (running) since Sat 2017-06-24 21:46:13 UTC; 1min ago
   Active: active (running) since Sat 2017-06-24 21:44:25 UTC; 1min ago
   Active: active (running) since Sat 2017-06-24 21:44:25 UTC; 1min ago

Novamente, estes serviços serão iniciados no Master, e não nos nodes/minions, visto que estes utilizarão outros serviços.

Agora vamos configurar o seguinte arquivo nos nodes/minions:

/etc/kubernetes/kubelet

Este arquivo apenas deverá ser editado nos nodes/minions, não no Master.

Vamos alterar o valor do parâmetro KUBELET_ADDRESS para que aceite comunicação não apenas do localhost:

1
KUBELET_ADDRESS="--address=0.0.0.0"

Descomentaremos também a linha KUBELET_PORT, para que possamos ter uma porta definida para a comunicação do kubelet:

1
2
# The port for the info server to serve on
KUBELET_PORT="--port=10250"

Vamos alterar o valor do parâmetro KUBELET_HOSTNAME para o nome que definimos para cada um dos minions, portanto em cada um deles este será um valor diferente. Supondo que este seja o minion1, utilizaremos:

1
KUBELET_HOSTNAME="--hostname-override=centos-minion1"

Vamos também alterar o valor para KUBELET_API_SERVER, apontando para o nosso Master:

1
KUBELET_API_SERVER="--api-servers=http://centos-master:8080"

Vamos comentar a linha KUBELET_POD_INFRA_CONTAINER, visto que não utilizaremos uma infraestrutura de containers externa, pois estaremos utilizando nossos próprios PODs e containers:

1
2
# pod infrastructure container
#KUBELET_POD_INFRA_CONTAINER="--pod-infra-container-image=registry.access.redhat.com/rhel7/pod-infrastructure:latest"

Nosso arquivo deverá estar assim: (Lembrando que o parâmetro KUBELET_HOSTNAME deverá ser diferente para cada um dos 3 minions, respectivamente: centos-minion1, centos-minion2 e centos-minion3)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
###
# kubernetes kubelet (minion) config

# The address for the info server to serve on (set to 0.0.0.0 or "" for all interfaces)
KUBELET_ADDRESS="--address=0.0.0.0"

# The port for the info server to serve on
KUBELET_PORT="--port=10250"

# You may leave this blank to use the actual hostname
KUBELET_HOSTNAME="--hostname-override=centos-minion1"

# location of the api-server
KUBELET_API_SERVER="--api-servers=http://centos-master:8080"

# pod infrastructure container
#KUBELET_POD_INFRA_CONTAINER="--pod-infra-container-image=registry.access.redhat.com/rhel7/pod-infrastructure:latest"

# Add your own!
KUBELET_ARGS=""

Uma vez que estas configurações também estão feitas nos 3 minions, vamos habilitar e iniciar os serviços necessários nos minions:

  • kube-proxy
  • kube-kubelet
  • docker
1
# systemctl enable kube-proxy kubelet docker
1
# systemctl start kube-proxy kubelet docker

Novamente, vamos ter certeza de que os 3 serviços estão rodando:

1
2
3
4
# systemctl status kube-proxy kubelet docker | grep "(running)"
   Active: active (running) since Sat 2017-06-24 21:44:23 UTC; 1h 16min ago
   Active: active (running) since Sat 2017-06-24 21:44:27 UTC; 1h 16min ago
   Active: active (running) since Sat 2017-06-24 21:44:27 UTC; 1h 16min ago

Novamente, estes 3 serviços devem ser habilitados e iniciados nos 3 minions.

Neste momento já temos nosso cluster rodando, com um master e 3 minions. :D

Testando o Cluster com o Kubernetes

Agora que temos a configuração básica de nosso Master Controller e de 3 minions, vamos testar nosso cluster.

Utilizaremos o utilitário kubectl (KubeControl) disponível com o kubernetes. Caso tenha interesse em ver os parâmetros e funções do mesmo… $ man kubectl

Vamos verificar a lista dos nodes ou minions que temos neste momento registrados em nosso Cluster. Vamos digitar alguns comandos em nosso Master Controller (centos-master):

1
2
3
4
5
[root@kalib1 ~]# kubectl get nodes
NAME             STATUS    AGE
centos-minion1   Ready     17m
centos-minion2   Ready     15m
centos-minion3   Ready     10m

Os três nodes criados e configurados anteriormente já são reconhecidos pelo nosso Kubernetes através do Master Controller. Além de registrados, estão com o status Ready, o que indica que estão prontos para funcionar e executar o que precisarmos.

Caso deseje conhecer mais parâmetros que a função get do kubectl possui, podemos invocar o manual desta função: $ man kubectl-get

Além do status, podemos conseguir diversas outras informações dos nodes através do kubectl: (Ex: kubectl describe nodes) Isto lhe daria informações sobre todos os nodes. Vamos experimentar com um node em específico.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
[root@kalib1 ~]# kubectl describe node centos-minion1
Name:                   centos-minion1
Role:
Labels:                 beta.kubernetes.io/arch=amd64
                        beta.kubernetes.io/os=linux
                        kubernetes.io/hostname=centos-minion1
Taints:                 <none>
CreationTimestamp:      Tue, 20 Jun 2017 19:27:31 +0000
Phase:
Conditions:
  Type                  Status  LastHeartbeatTime                       LastTransitionTime                      Reason     Message
  ----                  ------  -----------------                       ------------------                      ------     -------
  OutOfDisk             False   Sun, 25 Jun 2017 01:39:38 +0000         Fri, 23 Jun 2017 17:31:44 +0000         KubeletHasSufficientDisk    kubelet has sufficient disk space available
  MemoryPressure        False   Sun, 25 Jun 2017 01:39:38 +0000         Tue, 20 Jun 2017 19:27:31 +0000         KubeletHasSufficientMemory  kubelet has sufficient memory available
  DiskPressure          False   Sun, 25 Jun 2017 01:39:38 +0000         Tue, 20 Jun 2017 19:27:31 +0000         KubeletHasNoDiskPressure    kubelet has no disk pressure
  Ready                 True    Sun, 25 Jun 2017 01:39:38 +0000         Fri, 23 Jun 2017 17:31:54 +0000         KubeletReady                        kubelet is posting ready status
Addresses:              172.31.120.16,172.31.120.16,centos-minion1
Capacity:
 alpha.kubernetes.io/nvidia-gpu:        0
 cpu:                                   1
 memory:                                1015348Ki
 pods:                                  110
Allocatable:
 alpha.kubernetes.io/nvidia-gpu:        0
 cpu:                                   1
 memory:                                1015348Ki
 pods:                                  110
System Info:
 Machine ID:                    f9afeb75a5a382dce8269887a67fbf58
 System UUID:                   EC2C8A0E-91D6-F54E-5A49-534A6A903FDA
 Boot ID:                       20961efd-c946-481a-97cb-7788209551ae
 Kernel Version:                3.10.0-327.28.2.el7.x86_64
 OS Image:                      CentOS Linux 7 (Core)
 Operating System:              linux
 Architecture:                  amd64
 Container Runtime Version:     docker://1.12.6
 Kubelet Version:               v1.5.2
 Kube-Proxy Version:            v1.5.2
ExternalID:                     centos-minion1
Non-terminated Pods:            (0 in total)
  Namespace                     Name            CPU Requests    CPU Limits      Memory Requests Memory Limits
  ---------                     ----            ------------    ----------      --------------- -------------
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.
  CPU Requests  CPU Limits      Memory Requests Memory Limits
  ------------  ----------      --------------- -------------
  0 (0%)        0 (0%)          0 (0%)          0 (0%)
Events:
  FirstSeen     LastSeen        Count   From                            SubObjectPath   Type            Reason             Message
  ---------     --------        -----   ----                            -------------   --------        ------             -------
  15m           15m             1       {kubelet centos-minion1}                        Normal          Starting           Starting kubelet.
  15m           15m             1       {kubelet centos-minion1}                        Warning         ImageGCFailed      unable to find data for container /
  15m           15m             2       {kubelet centos-minion1}                        Normal          NodeHasSufficientDisk       Node centos-minion1 status is now: NodeHasSufficientDisk
  15m           15m             2       {kubelet centos-minion1}                        Normal          NodeHasSufficientMemory     Node centos-minion1 status is now: NodeHasSufficientMemory
  15m           15m             2       {kubelet centos-minion1}                        Normal          NodeHasNoDiskPressure       Node centos-minion1 status is now: NodeHasNoDiskPressure
  15m           15m             1       {kubelet centos-minion1}                        Warning         Rebooted           Node centos-minion1 has been rebooted, boot id: 20961efd-c946-481a-97cb-7788209551ae

Obviamente recebemos um retorno com muitas informações em formato Json, o que nem sempre é como esperamos. Existem formas de filtrar os resultados e conseguir informações mais precisas, como o bom e velho grep:

1
2
[root@kalib1 ~]# kubectl describe node centos-minion1 | grep Addresses
Addresses:              172.31.120.16,172.31.120.16,centos-minion1

Você também pode utilizar expressões regulares e a sintaxe do próprio Kubernetes para consultas mais complexas, como por exemplo, formatar a minha saída Json de forma a pegar apenas a listagem de status dos meus nodes que estão com Ready = True:

1
2
3
4
5
[root@kalib1 ~]# kubectl get nodes -o jsonpath='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}'| tr ';' "\n" | grep "Ready=True"

Ready=True
Ready=True
Ready=True

A sua criatividade é o limite. ;]

Não temos nenhum pod configurado, mas também poderíamos utilizar kubectl get para conseguir a listagem de nossos pods:

1
2
3
[root@kalib1 ~]# kubectl get pods

No resources found.

Criando pods

Assim como com o Docker, Ansible e algumas outras ferramentas, utilizaremos a linguagem YAML para criar nossos arquivos de configuração.

Criaremos um diretório chamado Builds em nosso Master Controller apenas para melhor organizar nossos arquivos de configuração e ficar mais fácil encontrá-los no futuro:

1
2
3
# mkdir Builds

# cd Builds

Para criarmos Pods, o que fazemos na verdade é criar arquivos de configuração que vão dizer ao Kubernetes qual o estado em que desejamos nossa infraestrutura. O papel do Kubernetes é ler esta configuração e assegurar que o estado de nossa infraestrutura reflita o estado desejado.

Para facilitar, vamos utilizar exemplos encontrados na própria documentação do Kubernetes. Comecemos com a criação de um Pod para um servidor web Nginx.

Vamos criar um arquivo chamado nginx.yaml dentro do diretório Builds que criamos anteriormente:

1
# vim nginx.yaml

No arquivo indicaremos alguns atributos ou variáveis, bem como seus respectivos valores:

  • apiVersion – Indica a versão da API do kubernetes utilizada
  • kind – o tipo de recurso que desejamos
  • metadata – dados referentes ao recurso desejado
  • spec – especificações sobre o que este recurso irá conter

Vamos criar um Pod contendo um único container rodando a versão 1.7.9 do nginx bem como disponibilizando a porta 80 para receber conexões. Este deverá ser o conteúdo do arquivo nginx.yaml:

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
    - name: nginx
      image: nginx:1.7.9
      ports:
      - containerPort: 80

Antes de executarmos, vamos nos certificar novamente de duas coisas:

  • Que realmente não temos nenhum Pod criado e ativo;
  • Que não temos nenhum container rodando em nossos nodes.

No centos-master:

1
2
3
[root@kalib1 Builds]# kubectl get pods

No resources found.

No centos-minion1: (Execute o mesmo comando nos demais nodes (centos-minion2 e centos-minion3))

1
2
3
[root@kalib2 ~]# docker ps

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

Novamente: Se você não faz ideia do que acabei de digitar, (docker ps) volte e leia um pouco sobre Docker antes de seguir com este artigo.

Como podemos ver, não temos nenhum Pod, bem como nenhum container rodando em nossos nodes.

Vamos utilizar kubectl create para criar o Pod utilizando o arquivo que criamos nginx.yaml: Executaremos este comando no Master Controller – centos-master

1
2
3
[root@kalib1 Builds]# kubectl create -f nginx.yaml

pod "nginx" created

O Kubernetes está dizendo que nosso Pod “nginx” foi criado. Vamos verificar:

1
2
3
4
[root@kalib1 Builds]# kubectl get pods

NAME      READY     STATUS    RESTARTS   AGE
nginx     1/1       Running   0          1m

O pod está criado e rodando. Agora, execute novamente docker ps nos 3 nodes para identificar em qual deles o container foi criado. Sim, como não especificamos nada, o Kubernetes vai verificar os recursos disponíveis no momento e vai lançar onde ele achar mais adequado.

1
2
3
4
5
[root@kalib4 ~]# docker ps

CONTAINER ID        IMAGE                                      COMMAND                  CREATED             STATUS              PORTS               NAMES
6de8e22e1536        nginx:1.7.9                                "nginx -g 'daemon off"   2 minutes ago       Up 2 minutes                            k8s_nginx.b0df00ef_nginx_default_d4debd3a-594c-11e7-b587-06827a5b32d4_583881e0
ae49b36ae11b        gcr.io/google_containers/pause-amd64:3.0   "/pause"                 2 minutes ago       Up 2 minutes                            k8s_POD.b2390301_nginx_default_d4debd3a-594c-11e7-b587-06827a5b32d4_fb5c834f

Sim, existem dois containers rodando. Um deles é o nosso “nginx”, enquanto que o outro é um container padrão do google chamado “/pause”, o qual será responsável pela manutenção de alguns recursos de nosso cluster.

Podemos novamente pedir a descrição deste pod que acabamos de criar:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
[root@kalib1 Builds]# kubectl describe pod nginx

Name:           nginx
Namespace:      default
Node:           centos-minion3/172.31.123.22
Start Time:     Sun, 25 Jun 2017 02:20:18 +0000
Labels:         <none>
Status:         Running
IP:             172.17.0.2
Controllers:    <none>
Containers:
  nginx:
    Container ID:               docker://6de8e22e153618271bb6e8095c68070126541331c8acfc3f5d1a654f4b978454
    Image:                      nginx:1.7.9
    Image ID:                   docker-pullable://docker.io/nginx@sha256:e3456c851a152494c3e4ff5fcc26f240206abac0c9d794affb40e0714846c451
    Port:                       80/TCP
    State:                      Running
      Started:                  Sun, 25 Jun 2017 02:20:27 +0000
    Ready:                      True
    Restart Count:              0
    Volume Mounts:              <none>
    Environment Variables:      <none>
Conditions:
  Type          Status
  Initialized   True
  Ready         True
  PodScheduled  True
No volumes.
QoS Class:      BestEffort
Tolerations:    <none>
Events:
  FirstSeen     LastSeen        Count   From                            SubObjectPath           Type            Reason     Message
  ---------     --------        -----   ----                            -------------           --------        ------     -------
  6m            6m              1       {default-scheduler }                                    Normal          Scheduled  Successfully assigned nginx to centos-minion3
  6m            6m              1       {kubelet centos-minion3}        spec.containers{nginx}  Normal          Pulling    pulling image "nginx:1.7.9"
  6m            6m              2       {kubelet centos-minion3}                                Warning         MissingClusterDNS   kubelet does not have ClusterDNS IP configured and cannot create Pod using "ClusterFirst" policy. Falling back to DNSDefault policy.
  6m            6m              1       {kubelet centos-minion3}        spec.containers{nginx}  Normal          Pulled     Successfully pulled image "nginx:1.7.9"
  6m            6m              1       {kubelet centos-minion3}        spec.containers{nginx}  Normal          Created    Created container with docker id 6de8e22e1536; Security:[seccomp=unconfined]
  6m            6m              1       {kubelet centos-minion3}        spec.containers{nginx}  Normal          Started    Started container with docker id 6de8e22e1536

Obviamente que isto é apenas a configuração mais básica que se possa imaginar, sem storage, mapeamentos de portas, redirecionamentos, rotas, etc. A ideia é apenas uma apresentação inicial..o que é o Kubernetes.

Happy Hacking

Recebendo Alarmes Do AWS Diretamente No Slack

| Comments

Antes de entrar na configuração dos serviços, talvez seja necessário apresentar o Slack, visto que muitos ainda não conhecem ou utilizam esta poderosa e versátil ferramenta de comunicação instantânea para times.

Slack é uma plataforma para comunicação entre times que desejam um ambiente mais dinâmico e ágil. Diferentemente de muitas plataformas de chat disponíveis, como o Google Hangouts, o Slack nos permite criar canais distintos com membros distintos de um mesmo time fazendo parte daquele canal específico. Não, não estou falando de chat em grupo, mas sim canais específicos que permitem integrações com serviços distintos, como receber notificações sobre commits feitos em um repositório ou branch específico no github, notificações de tickets abertos em ferramentas como o Jira, por exemplo, etc. O Slack é completamente programável e escalável, o que nos permite ter inúmeras funcionalidas.

Provavelmente não seja necessário apresentar o AWS, ou Amazon Web Services, visto que já está no mercado desde 2006, no entanto cabe um resumo para os que não estão familiarizados com o mesmo (embora o público alvo deste post seja quem já possui alguma familiaridade com AWS).

Aws ou Amazon Web Services é uma plataforma de serviços em nuvem segura, oferecendo poder computacional, armazentamento de banco de dados, distribuição de conteúdo e outras funcionalidades.

Por que eu deveria ter alarmes e notificações do AWS em um serviço de chat como o Slack quando já recebo estas notificações por email?

É verdade que o uso mais comum para envio de alarmes e notificações do AWS costuma ser via email, no entanto fica fácil identificar alguns problemas com este método. O principal e mais recorrente que vejo é o caso de as notificações caírem em um email específico visto por poucas pessoas (na maioria das vezes) ou nem visto sequer, pois geralmente as pessoas ficam cansadas de olhar notificações e ter sua caixa de entrada entupida com eles portanto criam filtros que jogam os emails de notificação para um diretório que dificilmente será checado.

Outro problema comum com esta prática é a demora até que alguém leia a notificação no meio de tantos outros na pasta ou filtro criado e, muitas vezes, quando se vê a notificação, o problema já está aguardando uma solução há horas.

Deixando claro, não estou defendendo a ideia de abolir as notificações por email. Eu mesmo utilizo ambos, afinal o email continua bastante eficiente para fins de armazenamento e checagem histórica, por exemplo.

Uma vez que nos dias atuais os times de TI estão cada vez mais unificados e dinâmicos, buscando incorporar uma mentalidade DevOps e Agile, a comunicação rápida e eficiente se torna um fator primordial para o sucesso de qualquer projeto. Ter um local centralizado para conversar com os demais membros do time, trocar arquivos, detalhes de projetos, receber notificações de commits, prazos, tickets, documentação e, por que não, notificações de monitoramento e alarmes, torna-se essencial.

Vamos então entender como funcionaria uma solução para enviar as notificações e alarmes do AWS para o Slack.

O que utilizaremos:

  1. No Slack:
    • Um plugin ou Slack App chamado Incoming WebHooks
    • O nome de um canal para envio das notificações
  2. No AWS:
    • Serviço SNS Topic
    • Serviço CloudWatch
    • Serviço Lambda Function

Vamos lá…

Slack

Vamos começar escolhendo o canal no Slack no qual desejo receber a minha notificação ou alarme: #devops

Estou supondo que você já utiliza o Slack e já possui um time criado no mesmo. Caso ainda não, crie um time no Slack seguindo os passos descritos no site oficial antes de seguir em frente… ;]

O próximo passo é configurar a integração instalando o Plugin ou Slack App Incoming WebHooks. Para isto, acesse a página de apps de seu time no Slack: https://SEUTIME.slack.com/apps

Pesquise por Incoming WebHooks e você terá apenas um resultado, portanto clique sem medo.

Clique no pequeno lápis que se encontrará no canto direito para editar as configurações do Incoming WebHook. Os únicos campos que precisaremos editar neste momento são os seguintes: * Post to Channel – Aqui indicarei o meu canal: #devops * Customize Name – Aqui indicarei um nome qualquer: AWS-Alerts

Importante: Repare que nesta página de configurações ele lhe passará uma entrada ou URL com o código para o seu WebHook. Esta informação estará listada em Webhook URL e será algo como: *https://hooks.slack.com/services/T434P71A4/U4G3JUG13/kPjvXY4Kd8wPm4TvrEqhN6Dv*. Copie esta informação em algum local de fácil acesso pois precisaremos desta URL para a configuração que faremos a seguir no AWS.

Salve suas configurações e vamos configurar os serviços do AWS para que nosso WebHook possa receber as informações devidamente.

Amazon Web Services

Se você já possui alguma familiaridade com o AWS, sabe que existem duas formas principais para administração e gerenciamento de nossos serviços: Pela interface web de gerenciamento (GUI) OU pela linha de comandos através da AWS CLI Tool que se comunica com a API do AWS. Este procedimento, assim como praticamente todos os outros, pode ser realizado por ambos os meios.

Se você também gosta de automação, provavelmente prefere utilizar a CLI, no entanto irei listar aqui o procedimento em ambos os meios.

Passo 1: Criando um SNS Topic para receber os alarmes

1.1 – Pela Interface Web de Gerenciamento (GUI)

  • A partir da Dashboard principal, clique ou busque pelo serviço SNS;
  • Crie um novo SNS Topic:
    • No menu da lateral esquerda, clique em Topics;
    • Clique em Create new topic;
    • Preencha os campos Name (obrigatório) e Display Name (opcional) para o seu tópico. Para este exemplo utilizarei aws-slack-alerts como Name e aws-slack como Display Name; (O Display Name só é necessário em caso de você também desejar enviar notificações por SMS)
    • Clique em Create Topic
  • Agora você já deve ser capaz de ver seu SNS Topic na lista.

1.2 – Pela AWS CLI Tool

Estou assumindo que se você optou por utilizar este método, é porque já possui sua CLI configurada e autenticando em sua conta do AWS com sua chave. Caso você não saiba do que estou falando, sugiro que siga a documentação oficial para isto.

  • Pela CLI tool, digite o seguinte comando, indicando a região na qual você deseja criar seu tópico e o nome desejado:
1
2
3
aws sns create-topic
    --region us-west-1
    --name aws-slack-alerts
  • IMPORTANTE: Você receberá um identificador (TopicArn) para este alarme. Você precisará dele no passo seguinte.
  • Caso queira ter certeza, você pode listar seus tópicos utilizando:
1
aws sns list-topics

Passo 2: Criando um Alarme no serviço CloudWatch

2.1 – Pela Interface Web de Gerenciamento (GUI)

  • A partir da Dashboard principal, clique ou busque pelo serviço CloudWatch;
  • Crie um novo Alarme:
    • Clique em Alarms;
    • Clique no botão Create Alarm;
    • Escolha a categoria do alarme desejado. Para este exemplo utilizarei ELB Metric > Per-LB Metrics (Dentre as várias categorias disponíveis, esta se refere à Load Balancers);
    • Selecione a métrica exata desejada. No caso deste exemplo, preciso selecionar a métrica e o Load Balancer desejado. Ao escolher a métrica e o alvo (em meu caso um Load Balancer) clique em Next. Neste exemplo eu escolhi a métrica HTTPCode_Backend_5XX (para monitorar 500 errors) e um Load Balancer chamado LB-GuySpyV3;
    • O próximo passo é definir um nome e uma descrição para este Alarme, bem como definir as triggers e períodos de monitoramento. Neste exemplo utilizei o nome LB-GuySpyV3-ELB_500 para meu alarme; (Não entrarei em detalhes quanto ao uso das triggers, visto que para cada tipo ou categoria de métrica, as triggers serão diferentes, bem como o cenário de seu ambiente e nível de criticidade. Em resumo, se você deseja monitorar o uso de CPU de um determinado servidor, a trigger seria o gatilho que ativaria o alarme, por exemplo: Só quero ser alarmado se o uso de CPU neste servidor ou instância for >= 90% e assim permanecer por pelo menos 60 segundos, ou por dois períodos seguidos de 60seg.)
    • Na seção Actions da configuração do Alarme defina o State e indique que a notificação deverá ser enviada (Send notification to) para o SNS Topic que criamos anteriormente. Para este exemplo optei por State is ALARM e decidi enviar as notificações para aws-slack-alerts, sendo este o SNS Topic que criei no início;
    • Finalize clicando em Create Alarm.

2.2 – Pela AWS CLI Tool

Novamente… Estou assumindo que se você optou por utilizar este método, é porque já possui sua CLI configurada e autenticando em sua conta do AWS com sua chave. Caso você não saiba do que estou falando, sugiro que siga a documentação oficial para isto.

  • Pela CLI tool, digite o seguinte comando, indicando os atributos abaixo:
    • region (Região);
    • alarm-name (Nome do alarme);
    • alarm-description (Descrição do alarme);
    • alarm-actions (Definir a ação do alarme – Apontar para o TopicArn do SNS Topic que criamos anteriormente);
    • metric-name (Nome da Métrica desejada);
    • namespace AWS/ELB —statistic (Estatística desejada para aquela métrica, neste caso utilizarei Sum (Soma) ao invés de Average (Média));
    • dimensions (O alvo desta métrica de monitoramento, no nosso caso um Load Balancer);
    • period e evaluation-periods (Períodos desejados para a trigger);
    • threshold (O valor desejado: Neste exemplo estou colocando o valor como 1, portanto receberei o alarme caso seja >= 1. Sim, eu sei que receberei o alarme a cada minuto, mas estou fazendo isto de propósito para recebermos a notificação a fim de teste. Nunca utilize um threshold desses em produção. :p);
    • comparison-operator (Operador de comparação desejado, neste caso >=);
1
2
3
4
5
6
7
8
9
10
11
12
aws cloudwatch put-metric-alarm --region us-west-1
    --alarm-name "LB-GuySpyV3-ELB_500"
    --alarm-description "Sends 500-errors to Slack"
    --actions-enabled
    --alarm-actions "TheTopicArn from last step"
    --metric-name "HTTPCode_Backend_5XX"
    --namespace AWS/ELB --statistic "Sum"
    --dimensions "Name=LoadBalancerName,Value=LB-GuySpyV3"
    --period 60
    --evaluation-periods 60
    --threshold 1
    --comparison-operator "GreaterThanOrEqualToThreshold"

Passo 3: Criando uma Função Lambda como Assinante (Subscriber) do nosso SNS Topic

3.1 – Pela Interface Web de Gerenciamento (GUI)

  • A partir da Dashboard principal, clique ou busque pelo serviço Lambda;
  • Crie uma Nova Função Lambda:
    • Clique em Create a Lambda Function;
    • Na tela Select Blueprint clique na opção cloudwatch-alarm-to-slack; (Você poderá precisar buscar por esta opção)
    • O próximo passo será a tela Configure Triggers. Selecione o SNS Topic que foi criado anteriormente (aws-slack-alerts neste exemplo) e marque a opção Enable Trigger e clique em Next;
    • Em Configure Function dê um Nome e uma Descrição para a função e escolha Node.js.4.3 como Runtime;
    • No campo Lambda Function Code cole o seguinte código: Disponível no github
      • (Você deverá setar os valores das variáveis CHANNEL e PATH, onde CHANNEL é o canal do Slack para o qual você deseja mandar as notificações e PATH é a URL de seu WebHook, recebida quando configuramos o Incoming WebHook no Slack)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
var https = require('https');
var util = require('util');

var CHANNEL = "#devops";
var PATH = "/services/T434P71A4/U4G3JUG13/kPjvXY4Kd8wPm4TvrEqhN6Dv";

exports.handler = function(event, context) {
    console.log(JSON.stringify(event, null, 2));
    console.log('From SNS:', event.Records[0].Sns.Message);

    var postData = {
        "channel": CHANNEL,
        "username": "AWS SNS",
        "text": "*" + event.Records[0].Sns.Subject + "*",
        "icon_emoji": ":aws:"
    };

    var message = event.Records[0].Sns.Message;
    var severity = "good";

    var dangerMessages = [
        " but with errors",
        " to RED",
        "During an aborted deployment",
        "Failed to deploy application",
        "Failed to deploy configuration",
        "has a dependent object",
        "is not authorized to perform",
        "Pending to Degraded",
        "Stack deletion failed",
        "Unsuccessful command execution",
        "You do not have permission",
        "Your quota allows for 0 more running instance"];

    var warningMessages = [
        " aborted operation.",
        " to YELLOW",
        "Adding instance ",
        "Degraded to Info",
        "Deleting SNS topic",
        "is currently running under desired capacity",
        "Ok to Info",
        "Ok to Warning",
        "Pending Initialization",
        "Removed instance ",
        "Rollback of environment"
        ];

    for(var dangerMessagesItem in dangerMessages) {
        if (message.indexOf(dangerMessages[dangerMessagesItem]) != -1) {
            severity = "danger";
            break;
        }
    }

    // Only check for warning messages if necessary
    if (severity == "good") {
        for(var warningMessagesItem in warningMessages) {
            if (message.indexOf(warningMessages[warningMessagesItem]) != -1) {
                severity = "warning";
                break;
            }
        }
    }

    postData.attachments = [
        {
            "color": severity,
            "text": message
        }
    ];

    var options = {
        method: 'POST',
        hostname: 'hooks.slack.com',
        port: 443,
        path: PATH
    };

    var req = https.request(options, function(res) {
      res.setEncoding('utf8');
      res.on('data', function (chunk) {
        context.done(null, postData);
      });
    });

    req.on('error', function(e) {
      console.log('problem with request: ' + e.message);
    });

    req.write(util.format("%j", postData));
    req.end();
};
  • O Handler deverá ser o default index.handler;
    • Para role selecione Create a custom role; (Isto será necessário apenas para a sua primeira função)
    • Na tela seguinte selecione lambda_basic_execution como IAM role e deixe o Policy Name com seu valor default. O AWS irá criar uma política de segurança padrão que nos dará os privilégios necessários. Clique em Allow;
    • Certifique-se de que o valor para VPC na seção Advanced Settings seja No VPC; Clique em Next, reveja suas configurações e clique em Create Function;
  • Aguarde seu alarme acontecer e receba a notificação no Slack. :D

O resultado em seu Slack será algo assim…

Parabéns, você já está recebendo suas notificações via Slack. Basta criar outros alarmes no AWS utilizando a mesma Lambda Function e o mesmo SNS Topic.

Happy Hacking!

Docker - Uma Alternativa Elegante Para Containers No Linux

| Comments

Antes de falar sobre o Docker, é importante que se entenda o conceito de um container, portanto vamos começar do básico. A imagem acima, logomarca do Docker, deixa claro o que é um container. A baleia, representando um navio, carregando diversos caixotes ou containers ilustra o conceito físico de um container. Nada mais do que um enorme caixote que possui o intuito de isolar algo. Quando um grande navio transporta mercadorias de um porto para outro, ele costuma trazer diversos containers separando estas mercadorias, de forma que as coisas não fiquem misturadas e bagunçadas. A forma de separação vai depender dos critérios de organização utilizados pela embarcação, seja por proprietário, seja por categoria de produtos, etc. De qualquer forma, embora cada container possua seus elementos próprios, todos os containers compartilham alguns recursos básicos, como por exemplo a embarcação, que é o meio de transporte para todos os containers ali contidos.

Da mesma forma se dá no mundo dos computadores, onde o conceito de containers surgiu para separar e isolar alguns recursos e aplicações, otimizando os recursos que servem como base e que podem ser utilizados de forma compartilhada, como por exemplo o kernel do Sistema Operacional. De certa forma isto nos faz lembrar um pouco da virtualização, onde cada máquina virtual compartilha os recursos da máquina física, no entanto existe uma diferença clara no contexto de containers, visto que em um cenário de virtualização você precisará possuir um SO instalado na máquina física, com seu kernel e todos os seus recursos, e um SO instalado em sua máquina virtual, também com seu kernel e todos os seus recursos. Quando falamos em containers, imagine que você só precisará do kernel, bem como vários outros recursos, na máquina que será a hospedeira do container (a embarcação).

Containers Linux surgiram como uma tecnologia chave para empacotamento e entrega de aplicativos, combinando a leveza do isolamento de aplicativos com a flexibilidade de métodos de deploy baseados em imagens.

Uma das formas mais simples de se imaginar a vantagem da utilização de containers é imaginar que você possui uma empresa que hospeda servidores de aplicações para seus clientes. Se um novo cliente surge querendo hospedar a aplicação dele, você subirá uma nova máquina virtual, o que inclui todo um novo sistema operacional, enquanto que em uma solução baseada em containers você poderá ter apenas a sua máquina com um único kernel Linux provendo as priorizações de recursos (CPU, memória, I/O, rede, etc.) sem a necessidade de dar boot em um novo sistema operacional (máquinia virtual) na qual rodará a aplicação deste cliente.

Dizem que uma imagem vale mais que mil palavras…

Na imagem acima temos o cenário convencional com a utilização de Máquinas Virtuais. Em suma, temos um host físico, com seu respectivo SO e kernel. Acima deles temos a camada de virtualização ou HyperVisor, enquanto que acima desta teremos as máquinas virtuais, com seus respectivos SOs (cada um com seu kernel) instalados. No caso temos 3 VMs, com 3 SOs (cada um com seu kernel). Na camada acima encontramos o que realmente é necessário para o app do cliente funcionar, que são as bibliotecas e os binários. Por fim, o App do cliente em si.

Vejamos como fica o cenário com a utilização de containers, docker neste caso…

No cenário com o Docker percebemos que a camada de SO das VMs sumiu, visto que ela não é mais necessária. Ao invés de Máquinas Virtuais, agora nós temos 3 containers, onde cada container roda os binários e bibliotecas de um SO, porém se aproveitando do kernel já existente no Host.

Com este grau de modularização nós ganhamos maior flexibilidade e agilidade no deploy de ambientes e aplicações.

Uma das vantagens da utilização do Docker é a existência de um repositório de imagens prontas que ficam disponibilizadas livremente para quem desejar utilizar. Seja uma imagem pronta de um container com CentOS, Ubuntu, etc.. Já existem centenas e centenas de imagens prontas para uso, sendo esta uma base de compartilhamento comunitário, mas…

Vamos ao que interessa…

Nos exemplos a seguir, estou utilizando o Ubuntu Server 15.04, visto que estou atualmente realizando uma POC de VPS com um novo host, portanto aproveitarei para fazer disto uma parte de meus testes nesta VPS. Sinta-se livre para utilizar sua máquina física com Ubuntu, com Debian, ou mesmo uma máquina virtual, caso não goste de realizar testes em sua máquina física, o resultado será o mesmo. Para que tudo funcione como esperamos, só existem 2 pré-requisitos a serem atendidos:

1- O kernel do Linux que será utilizado deve ser igual ou superior ao 3.8;

2- Caso você esteja realizando os testes em uma VM, seria interessante que sua máquina física tivesse comunicação com a VM. Isso pode ser testado com um ping da máquina física para a VM. No caso de sua máquina virtual ser instalada com interface gráfica, esta comunicação não será necessária, pois o único momento em que utilizaremos isto será para abrir um navegador e fazer um teste de acesso ao endereço da máquina virtual.

Vamos lá. Para ter a certeza de que você atende o pré-requisito de kernel, utilize o comando “uname -r”:

1
2
 kalib@cloudcaverna:~$ uname -r
 3.19.0-25-generic

Estou com o kernel 3.19, portanto superior ao kernel 3.8 que é o pré-requisito mínimo. Vamos em frente.

Primeiramente, vamos instalar o Docker. Seja lá qual for sua distribuição Linux, digite o comando: (O comando deve ser executado com o usuário root ou com o comando sudo!)

1
 # curl -sSL https://get.docker.com | sh

Ele baixará e executará um script de instalação, no meu caso do Ubuntu ele irá instalar um repositório e em seguida instalará o docker. O próximo passo será iniciar o serviço do docker:

1
2
 # /etc/init.d/docker start
 [ ok ] Starting docker (via systemctl): docker.service.

O docker possui uma série de scripts/comandos próprios para facilitar a sua administração, como por exemplo um script de ps, para que possamos ter a certeza de que ele está rodando e, além disso, saber se existem containers em execução, da mesma forma que faríamos com o ps do linux para ver os processos em andamento.

1
2
3
 # docker ps

 CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

Podemos ver que o docker está rodando, no entanto nenhum container está em execução. Na verdade, não temos nenhum container criado, portanto obviamente não poderia estar em execução.

Além do ps, podemos utilizar o script images para ver quais imagens de containers já possuímos para uso:

1
2
3
 # docker images

 REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE

Da mesma forma, não temos ainda nenhuma imagem baixada para uso.

Uma vez que estamos falando de containers, conforme dito anteriormente, a ideia é isolar ao máximo e otimizar o que precisamos para este container, portanto precisamos informar o processo que desejamos iniciar no container em questão.

Vamos criar um container do Ubuntu, por exemplo, na versão 15.04, lançada em Abril deste ano, e vamos iniciar juntamente com ele o processo /bin/bash. O comando utilizado será: docker run -i -t ubuntu:15.04 /bin/bash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 # docker run -i -t ubuntu:15.04 /bin/bash

 Unable to find image 'ubuntu:15.04' locally
 15.04: Pulling from library/ubuntu

 6e6a100fa147: Pull complete
 13c0c663a321: Pull complete
 2bd276ed39d5: Pull complete
 013f3d01d247: Already exists
 library/ubuntu:15.04: The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security.

 Digest: sha256:b2d4940544e515d4bc62b2a9ad3e6137b3e1e0937a41fdc1f0f30d12935e5b09
 Status: Downloaded newer image for ubuntu:15.04

 root@d70562e7533c:/#

É importante reparar que na primeira linha de execução ele me trouxe um alerta informando que não foi possível encontrar a imagem “ubuntu:15.04” localmente. Como disse acima, não temos ainda nenhuma imagem baixada, portanto ele não encontrou localmente e foi baixar diretamente no repositório de imagens do docker.

O procedimento foi extremamente rápido, certo? Acredite ou não, você já possui um container Ubuntu rodando em sua máquina. ;] Ainda não acredita? Repare novamente no seu prompt de comandos, veja que logo que ele finalizou o processo ele lhe deixou em um prompt “estranho”. No caso do meu exemplo acima, perceba que ao concluir o processo ele me deixou com o prompt assim:

1
 root@d70562e7533c:/#

Não, minha máquina não se chama d70562e7533c. Tenho certeza de que a sua também não se chama.. seja lá qual for a combinação de caracteres que lhe foi apresentada no prompt. Na verdade, sempre que iniciamos um container, o comportamento default é que você já é logado nele. Em suma, seu container com ubuntu 15.04 já foi criado e você já está logado nele, e esta combinação de caracteres estranha é o ID que foi dado ao seu container.

Ainda não acredita? Bom, você pode, por exemplo, dar um cat /etc/issue, para ver que você está de fato rodando um ubuntu 15.04. Claro, no meu caso não haverá diferença, pois minha máquina que está rodando o docker também é ubuntu 15.04.

1
2
3
4
5
 root@d70562e7533c:/# cat /etc/issue

 Ubuntu 15.04 \n \l

 root@d70562e7533c:/#

O*utro teste, seria rodar ps -ef** no container. Você verá que não existe nenhum processo rodando. Aliás, haverá apenas 1 processo (além do próprio ps), que foi o processo indicado na criação: /bin/bash. Desta forma você terá a certeza de que não está no prompt de sua máquina mesmo, visto que na sua certamente existem dezenas ou centenas de processos rodando, do kernel, do usuário, etc.

1
2
3
4
5
6
7
 root@d70562e7533c:/# ps -ef

 UID        PID  PPID  C STIME TTY          TIME CMD
 root         1     0  0 13:36 ?        00:00:00 /bin/bash
 root         9     1  0 13:44 ?        00:00:00 ps -ef

 root@d70562e7533c:/#

Da mesma forma você poderá experimentar outros comandos para testar (caso ainda não esteja acreditando que de forma tão “oi, simples assim” você já está com seu container pronto): ls, apt-get update, etc.. Tudo funcionando como se fosse uma máquina real, ou virtual, no entanto sem um kernel, visto que ela está utilizando o kernel da máquina host.

Agora que temos a certeza de que estamos em nosso container, você pode sair do container e voltar para sua máquina host. Para isso você precisará pressionar as teclas Ctrl + P + Q. Desta forma, você verá que seu prompt voltará para seu host enquanto que seu container continuará rodando, você apenas saiu do prompt do mesmo.

1
2
3
 root@d70562e7533c:/# root@cloudcaverna:~#

 root@cloudcaverna:~#

Vamos verificar novamente o ps do docker, visto que da última vez ele estava vazio. Desta vez ele nos mostrará um processo em execução, que no caso é o container que criamos.

1
2
3
4
 root@cloudcaverna:~# docker ps

 CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
 d70562e7533c        ubuntu:15.04        "/bin/bash"         15 minutes ago      Up 15 minutes                           modest_khorana

No retorno podemos ver o ID do nosso container, o nome da imagem que ele utiliza, o comando em execução, o tempo desde sua criação, o seu status, portas e nome.

O container ID poderá ser utilizado para voltar para nosso container através do comando docker attach <container-id>: (Após digitar o comando, pressione novamente Enter para liberar o prompt)

1
2
3
 root@cloudcaverna:~# docker attach d70562e7533c

 root@d70562e7533c:/#

No exemplo anterior nós utilizamos a combinação Ctrl + P + Q para sair do container mantendo-o rodando. Vamos experimentar utilizar desta vez Ctrl + D. Desta forma você não apenas está saindo mas também desligando o container. Execute novamente a lista de processos/containers do docker para ver:

1
2
3
 root@cloudcaverna:~# docker ps

 CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

No entanto, é importante lembrar que a imagem do Ubuntu 15.04 que baixamos, continua disponível para o caso de precisarmos criar novos containers Ubuntu 15.04:

1
2
3
4
 root@cloudcaverna:~# docker images

 REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
 ubuntu              15.04               013f3d01d247        17 hours ago        131.4 MB

Simples… Mas vamos fazer algo mais próximo do mundo real, afinal um container apenas com o Ubuntu rodando, ou qualquer outra que seja a distribuição escolhida, não tem muita utilidade, portanto vamos criar um container rodando um servidor web, para o caso de querermos hospedar um site ou aplicalção web neste container. Por ser mais leve e simples, vou utilizar o nginx no exemplo. Vamos utilizar o comando docker run -i -t -p 8080:80 ubuntu:15.04 /bin/bash

No comando acima estamos dizendo que queremos criar um novo container ubuntu 15.04 rodando o /bin/bash. Desta vez temos um parâmetro que não utilizamos anteriormente. O p serve para indicar a utilização de portas. Quando utilizamos p 8080:80 estamos dizendo que vamos utilizar a porta 80 no container e que ela estará mapeada na porta 8080 do nosso host ou hospedeiro. Ou seja, quando instalarmos o Nginx no ubuntu do container, você poderá através de seu host abrir o navegador e acessar o seu endereço ip ou nome de host (caso você possua resolução de nomes funcionando) na porta 8080.

1
2
3
 root@cloudcaverna:~# docker run -i -t -p 8080:80 ubuntu:15.04 /bin/bash

 root@08abb8611700:/#

O processo foi bem mais rápido desta vez, visto que já tínhamos a imagem do container ubuntu 15.04, portanto não tivemos a necessidade de baixar outra imagem. O seu container já está criado e você já está logado no prompt do mesmo, conforme pode ver através do ID do container que apareceu em seu prompt.

Vamos agora instalar o servidor web nginx para realizarmos o nosso teste. Para isso vamos atualizar os repositórios e em seguida instalar o pacote: apt-get update && apt-get install -y nginx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 root@08abb8611700:/# apt-get update && apt-get install -y nginx

 Ign http://archive.ubuntu.com vivid InRelease
 Ign http://archive.ubuntu.com vivid-updates InRelease
 Get:1 http://archive.ubuntu.com vivid/main Sources [1358 kB]
 Get:2 http://archive.ubuntu.com vivid/restricted Sources [7100 B]
 ...
 Building dependency tree       
 Reading state information... Done
 The following extra packages will be installed:
  fontconfig-config fonts-dejavu-core geoip-database init-system-helpers libexpat1 libfontconfig1 libfreetype6 libgd3 libgeoip1 libicu52
  libjbig0 libjpeg-turbo8 libjpeg8 libpng12-0 libssl1.0.0 libtiff5 libvpx1 libx11-6 libx11-data libxau6 libxcb1 libxdmcp6 libxml2 libxpm4
  libxslt1.1 nginx-common nginx-core sgml-base ucf xml-core
 Suggested packages:
  libgd-tools geoip-bin fcgiwrap nginx-doc ssl-cert sgml-base-doc debhelper
 The following NEW packages will be installed:
  fontconfig-config fonts-dejavu-core geoip-database init-system-helpers libexpat1 libfontconfig1 libfreetype6 libgd3 libgeoip1 libicu52
  libjbig0 libjpeg-turbo8 libjpeg8 libpng12-0 libssl1.0.0 libtiff5 libvpx1 libx11-6 libx11-data libxau6 libxcb1 libxdmcp6 libxml2 libxpm4
  libxslt1.1 nginx nginx-common nginx-core sgml-base ucf xml-core
 0 upgraded, 31 newly installed, 0 to remove and 0 not upgraded.
 Need to get 14.0 MB of archives.
 After this operation, 53.3 MB of additional disk space will be used.
 ...
 Get:1 http://archive.ubuntu.com/ubuntu/ vivid/main libexpat1 amd64 2.1.0-6ubuntu1 [70.6 kB]
 Processing triggers for systemd (219-7ubuntu6) ...

 root@08abb8611700:/#

Cortei bastante a saída visto ser desnecessária, mas uma vez que o nginx esteja instalado, vamos iniciá-lo: /etc/init.d/nginx start. Em seguida, vamos utilizar o ps para ver os processos que estão rodando no nosso container:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 root@08abb8611700:/# /etc/init.d/nginx start

 root@08abb8611700:/# ps -ef

 UID        PID  PPID  C STIME TTY          TIME CMD
 root         1     0  0 14:10 ?        00:00:00 /bin/bash
 root       609     1  0 14:19 ?        00:00:00 nginx: master process /usr/sbin/nginx
 www-data   610   609  0 14:19 ?        00:00:00 nginx: worker process
 www-data   611   609  0 14:19 ?        00:00:00 nginx: worker process
 www-data   612   609  0 14:19 ?        00:00:00 nginx: worker process
 www-data   613   609  0 14:19 ?        00:00:00 nginx: worker process
 root       614     1  0 14:19 ?        00:00:00 ps -ef

 root@08abb8611700:/#

Feito isto, o serviço está rodando e já pode ser testado. Em seu host você poderá abrir o navegador e acessar o endereço local de seu host, com a porta 8080, visto que foi esta que definimos inicialmente para mapear a porta 80 do container: localhost:8080

Caso esteja utilizando uma máquina virtual para fazer seus testes, você terá duas possibilidades: 1- Caso sua máquina virtual possua alguma interface gráfica instalada, você poderá abrir o navegador da própria VM e acessar o mesmo endereço de localhost com a porta 8080; 2- Caso sua VM não esteja com nenhum ambiente gráfico instalado, você poderá utilizar aplicações de CLI para testar (ex: lynx, curl, etc.) ou usar o navegador da máquina que serve de host para sua VM, levando em conta que você fez o teste descrito no início para ter certeza de que sua máquina física consegue se comunicar com sua VM. Neste caso, em sua máqunina física você acessará o endereço de sua vm no navegador, com a porta 8080.

Resultado? O Nginx em nosso container rodando perfeitamente.

Da mesma forma feita anteriormente, podemos sair de nosso container com a combinação de teclas Ctrl + P + Q e verificar os processos/containers do docker em execução:

1
2
3
4
5
6
7
8
 root@08abb8611700:/# root@cloudcaverna:~#

 root@cloudcaverna:~# docker ps

 CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
 08abb8611700        ubuntu:15.04        "/bin/bash"         19 minutes ago      Up 19 minutes       0.0.0.0:8080->80/tcp   jolly_hawking

 root@cloudcaverna:~#

É importante salientar que quando saímos do container com a combinação Ctrl + P + Q nós não estamos fechando o container, pois ele continua rodando, conforme pode ser visto com docker ps. No entanto, quando saímos do nosso antigo container com a combinação Ctrl + D, nós percebemos que ele finalizou de vez o container. Além de finalizar, ele excluiu o nosso container, visto que o mesmo não foi salvo, ou “comittado”. Se sairmos deste container no qual instalamos o nginx utilizando Ctrl + D, nós estaremos descartando tudo o que foi feito nele. Para poder finalizar o seu container sem perdê-lo, ou seja, mantendo o container salvo e tudo o que foi instalado/configurado nele, nós precisamos sair do container com a combinação Ctrl + P + Q, conforme fizemos acima, e em seguida realizar um commit deste container. Estaremos então criando uma imagem com o atual estado do container. Desta forma, se posteriormente precisarmos subir um novo container que rode o SO ubuntu 15.04 e que também possua o nginx, poderemos criar um novo container a partir desta imagem em poucos segundos. O commit se dá da seguinte forma: docker commit <container-id> <nome-que-vc-desejar>

Lembrando que o id do container pode ser conseguido através do comando docker ps e que o nome-que-vc-desejar será o nome utilizado para identificar esta sua máquina/imagem.

1
2
3
4
5
 root@cloudcaverna:~# docker commit 08abb8611700 cloudcaverna/ubuntu-nginx:1.0

 b0922bc8f41295cadadbd131c075e29288b52e8bb2d9546cb7c0327eb95fe7dc

 root@cloudcaverna:~#

Utilizei 1.0 ao final para ter uma ideia de versionamento, visto que o docker nos permite trabalhar desta forma. É uma questão de organização.

Em seguida você pode verificar sua imagem criada através do script images do docker, bem como o seu container ainda rodando através do script ps:

1
2
3
4
5
6
7
8
9
10
11
12
 root@cloudcaverna:~# docker images

 REPOSITORY                  TAG                 IMAGE ID            CREATED              VIRTUAL SIZE
 cloudcaverna/ubuntu-nginx   1.0                 b0922bc8f412        About a minute ago   204.6 MB
 ubuntu                      15.04               013f3d01d247        18 hours ago         131.4 MB

 root@cloudcaverna:~# docker ps

 CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
 08abb8611700        ubuntu:15.04        "/bin/bash"         49 minutes ago      Up 49 minutes       0.0.0.0:8080->80/tcp   jolly_hawking

 root@cloudcaverna:~#

Vamos agora criar um novo container. Aqui você pode escolher como prosseguir. Eu criarei um container com a distribuição Arch Linux rodando, mas você pode seguir e criar outra com o Ubuntu caso deseje, então vejamos as opções:

Opção 1 – Caso escolha criar este segundo container como Arch Linux

1
2
3
4
5
6
7
8
9
10
11
 root@cloudcaverna:~# docker run -i -t -p 6660:80 base/archlinux /bin/bash

 Unable to find image 'base/archlinux:latest' locally
 latest: Pulling from base/archlinux

 b31c6c1462e6: Pull complete
 b97e110c94d9: Already exists
 Digest: sha256:7905fad7578b9852999935fb0ba9c32fe16cece9e4d1d742a34f55ce9cebdfd1
 Status: Downloaded newer image for base/archlinux:latest

 [root@be266bf7e5a3 /]#

Opção 2 – Caso deseje criar um novo container Ubuntu aproveitando a imagem que já está pronta e com nginx já instalado

1
 docker run -i -t -p 6660:80 <nome-que-vc-deu-para-sua-imagem>

Como eu dei o nome de cloudcaverna/ubuntu-nginx, eu utilizaria docker run -i -t -p 6660:80 cloudcaverna/ubuntu-nginx.

Desta forma você não precisará sequer instalar o nginx novamente neste container, visto que você utilizou como base uma imagem que já possuía o nginx instalado, restando apenas a você testar no navegador novamente o endereço porém trocando a porta para 6660.

Como eu resolvi seguir em frente com um container totalmente novo, baseado no Arch Linux, vou dar um cat /etc/issue para ver que realmente estou em um ambiente com a distribuição Arch Linux e vou instalar o nginx nele com o pacman, visto que este é o gerenciador de pacotes do Arch:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 [root@be266bf7e5a3 /]# cat /etc/issue

 Arch Linux \r (\l)

 [root@be266bf7e5a3 /]# pacman -Sy nginx

 :: Synchronizing package databases...
 core                                                        121.2 KiB   203K/s 00:01 [#################################################] 100%
 extra                                                      1773.8 KiB   644K/s 00:03 [#################################################] 100%
 community                                                     2.7 MiB   936K/s 00:03 [#################################################] 100%
 resolving dependencies...
 looking for inter-conflicts...

 Packages (1): nginx-1.8.0-1

 Total Download Size:    0.34 MiB
 Total Installed Size:   0.98 MiB

 :: Proceed with installation? [Y/n]
 :: Retrieving packages ...
 nginx-1.8.0-1-x86_64                                        349.5 KiB   266K/s 00:01  [#################################################] 100%
 (1/1) installing nginx                                                                [ #################################################] 100%

 [root@be266bf7e5a3 /]#

Agora que estou com o nginx rodando também neste container do Arch Linux, vou sair do container pressionando a combinação Ctrl + P + Q e em seguida rodar o script de ps do docker:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 [root@be266bf7e5a3 /]# root@cloudcaverna:~#

 root@cloudcaverna:~# docker ps

 CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
 be266bf7e5a3        base/archlinux      "/bin/bash"         10 minutes ago      Up 10 minutes       0.0.0.0:6660->80/tcp   cocky_hoover
 08abb8611700        ubuntu:15.04        "/bin/bash"         About an hour ago   Up About an hour    0.0.0.0:8080->80/tcp   jolly_hawking

 root@cloudcaverna:~#
 ```

 **D**esta vez eu possuo dois containers criados, sendo um com o ubuntu 15:04 e outro com o Arch Linux. Agora vou realizar o commit do meu container Arch Linux com nginx, para não perder esta imagem. Em seguida, executarei o script **images** para ver as imagens que já possuo:

 ```
  root@cloudcaverna:~# docker commit be266bf7e5a3 cloudcaverna/archlinux-nginx:1.0

 f4ea14bf23c47466fb256fff9e3ab32ca85fb0256a05007ef4972ad7ff5f2aa9

 root@cloudcaverna:~# docker images

 REPOSITORY                     TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
 cloudcaverna/archlinux-nginx   1.0                 f4ea14bf23c4        5 seconds ago       285.9 MB
 cloudcaverna/ubuntu-nginx      1.0                 b0922bc8f412        20 minutes ago      204.6 MB
 ubuntu                         15.04               013f3d01d247        18 hours ago        131.4 MB
 base/archlinux                 latest              b97e110c94d9        8 weeks ago         278.8 MB

 root@cloudcaverna:~#

Agora vou testar no navegador o nginx deste meu segundo container, o qual defini que utilizaria a porta 6660 do meu host:

Repare que acessei ambos os endereços, tanto o da porta 8080, o qual está apresentando o nginx do meu primeiro container, quanto o da porta 6660, que apresenta o nginx do meu segundo container. Ambos funcionando em paralelo, com ambientes distintos, sendo um Ubuntu e o outro Arch Linux, porém ambos compartilham o mesmo kernel, que é o do meu host.

O próprio script ps do docker lhe informa as portas que estão sendo utilizadas para cada container, caso você esteja em dúvida:

1
2
3
4
5
6
7
 root@cloudcaverna:~# docker ps

 CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
 be266bf7e5a3        base/archlinux      "/bin/bash"         26 minutes ago      Up 26 minutes       0.0.0.0:6660->80/tcp   cocky_hoover
 08abb8611700        ubuntu:15.04        "/bin/bash"         About an hour ago   Up About an hour    0.0.0.0:8080->80/tcp   jolly_hawking

 root@cloudcaverna:~#

Da mesma forma, você poderá fazer toda e qualquer operação que você faria em uma máquina qualquer, como por exemplo monitorar os logs de acesso do nginx. Basta se conectar em algum dos dois containers com docker attach <container-id> e em seguida abrir o arquivo de log do nginx com o tail: tail -f /var/log/nginx/access.log. Com o log rodando, pode acessar novamente no navegador o endereço com a porta que está mapeada para este container e você verá os logs do seu acesso.

Anteriormente nós vimos que com a combinação Ctrl + P + Q eu consigo sair do container porém ele permanecerá rodando. Com Ctrl + D eu finalizava o container de vez. Mas, supondo que eu queira parar temporariamente o container, posso utilizar o script stop do container inserindo o id do container que desejo parar:

1
2
3
4
 root@cloudcaverna:~# docker stop be266bf7e5a3
 be266bf7e5a3

 root@cloudcaverna:~#

Existe ainda uma forma de ver o que foi alterado no container desde sua criação. É literalmente uma espécie de “diff”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 root@cloudcaverna:~# docker diff 08abb8611700
 C /.wh..wh.plnk
 A /.wh..wh.plnk/93.1190648
 C /bin
 A /bin/running-in-container
 A /core
 C /etc
 C /etc/default
 A /etc/default/nginx
 A /etc/fonts
 A /etc/fonts/conf.avail
 A /etc/fonts/conf.avail/10-antialias.conf
 A /etc/fonts/conf.avail/10-autohint.conf
 A /etc/fonts/conf.avail/10-hinting-full.conf
 A /etc/fonts/conf.avail/10-hinting-medium.conf
 A /etc/fonts/conf.avail/10-hinting-slight.conf
 A /etc/fonts/conf.avail/10-hinting.conf
 A /etc/fonts/conf.avail/10-no-sub-pixel.conf
 A /etc/fonts/conf.avail/10-scale-bitmap-fonts.conf
 A /etc/fonts/conf.avail/10-sub-pixel-bgr.conf
 A /etc/fonts/conf.avail/10-sub-pixel-rgb.conf
 A /etc/fonts/conf.avail/10-sub-pixel-vbgr.conf
 A /etc/fonts/conf.avail/10-sub-pixel-vrgb.conf
 A /etc/fonts/conf.avail/10-unhinted.conf
 A /etc/fonts/conf.avail/11-lcdfilter-default.conf
 A /etc/fonts/conf.avail/11-lcdfilter-legacy.conf
 A /etc/fonts/conf.avail/11-lcdfilter-light.conf
 ...
 ...

A saída é extensa, portanto cortei aqui mesmo, mas você terá basicamente todo o diff do que foi alterado desde a criação do container.

Resumidamente, esta é a função do docker. Com criatividade e disposição se faz muita coisa com ele. Não é a toa que os big players de mercado já estão utilizando bastante esta ferramenta para soluções de container, como por exemplo: Amazon, Apcera, Cisco, CoreOS, Datera, VMWare, Verizon Labs, Red Hat, Google, RackSpace, Oracle, IBM, Intel, Microsoft, HP, etc.

Lembrando que o endereço de repositórios com as imagens já existentes e disponibilizadas gratuitamente é https://hub.docker.com

Até a próxima…

Happy Hacking!