Como criar e manipular contêineres no Docker

Há várias formas de iniciar o uso do Docker e fazer uso dos containers. Neste texto, vou me concentrar nos comandos de execução — a começar por alguns comandos de verificação do estado da sua instalação.
Para saber se o Docker foi instalado corretamente, use o parâmetro ‘info’, na linha de comando:

docker info

O comando deve retornar algo semelhante ao que se vê abaixo:

Containers: 48
Images: 18
Storage Driver: aufs
 Root Dir: /var/lib/docker/aufs
 Dirs: 114
Execution Driver: native-0.2
Kernel Version: 3.13.0-63-generic
WARNING: No swap limit support

A saída do comando vai depender da configuração atual do seu sistema e do Docker.

O Docker trabalha sob a perspectiva cliente/servidor.
O mesmo binário Docker é usado tanto pelo servidor quanto pelos comandos do cliente.
Desta forma, quando executamos os comandos do lado do cliente, estes são repassados ao daemon Docker, em execução no seu sistema.

Para rodar um contêiner, use o comando ‘run’ (do próprio Docker).
Note que, nos exemplos deste artigo, o docker é usado pelo usuário normal — sem privilégios administrativos.
Na configuração padrão do aplicativo, é necessário rodá-lo precedido do sudo ou como root.
Leia mais sobre como configurar o docker para rodar com os privilégios de usuário comum, aqui.
O comando que segue, vai colocar a imagem do Ubuntu (ou a tag mais atual dele) para rodar como um contêiner, com uma shell bash dentro:

docker run -i -t ubuntu /bin/bash

Veja o que mais foi feito acima:

  • -i pede para que a entrada ou input permaneça aberta, para receber instruções
  • -t pede ao contêiner para atribuir-lhe um terminal
  • /bin/bash é a opção de programa terminal escolhida

Se vocẽ abrir uma nova janela do console (Ctrl + Alt + T, no Ubuntu), agora, vai poder verificar que o contêiner está rodando. Use o comando ‘ps’ do Docker, para isto:

docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
bdaaca495be7        ubuntu:latest       /bin/bash           10 seconds ago      Up 10 seconds                           jolly_meitner

O Docker é um excelente construtor de blocos para sistemas distribuídos e automatizados — o que inclui aplicações web de larga escala, clusters de bancos de dados, sistemas de distribuição/desenvolvimento continuado, PaaS privado, arquiteturas orientadas a serviço etc.
(Manual do Docker)

Os contêineres podem ser identificados e chamados pelos seus IDs. Tendo vários no seu sistema, cada contêiner terá sua própria identificação.
Se você acrescentar a opção ‘-a’, o Docker irá mostrar mais — o que inclui os contêineres que já estão parados no sistema.
dokcer-ps--a
Note as colunas das extremidades: à esquerda fica a ID de cada contêiner. À direita, o seu nome.
Você pode se referir a um contêiner, pela sua ID ou, se preferir, pelo seu nome.
O Docker “batiza” todos os novos contêineres criados sem nome. Ele cria/escolhe nomes aleatórios — jolly_meitner, goofy_hopper, stoic_torvalds etc. (Veja a figura, acima).

Como nomear contêineres Docker

Como já mencionei, é possível (e opcional) dar um nome a cada novo contêiner criado no Docker.
Caso o usuário não o faça, o Docker cria um nome aleatório e o atribui ao novo contêiner, no momento de sua criação.
Mas você pode (e deve) dar nomes que tenham mais significado para você.
Use o parâmetro ‘–name’ para escolher e atribuir um nome ao seu contêiner:

docker run --name primeiro_conteiner -i -t ubuntu /bin/bash

De dentro de um contêiner, use o comando ‘exit’ para terminá-lo e sair.

Opções para parar e iniciar um contêiner Docker

Você pode iniciar e parar um contêiner, com o uso dos comandos ‘start’ e ‘stop’, seguido da ID ou do nome do contêiner que você deseja atingir:

docker start 697f662350ab
697f662350ab
docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
697f662350ab        ubuntu:latest       /bin/bash           7 minutes ago       Up 6 seconds                            primeiro_conteiner   
bdaaca495be7        ubuntu:latest       /bin/bash           About an hour ago   Up About an hour                        jolly_meitner        
docker stop primeiro_conteiner
primeiro_conteiner
docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
bdaaca495be7        ubuntu:latest       /bin/bash           About an hour ago   Up About an hour                        jolly_meitner

docker-start-stop
Como você pode ver, nos exemplos acima (tanto no texto quanto na imagem), é possível se referir tanto ao ID quanto ao nome de um contêiner.
Ao aplicar os exemplos, você deve ter percebido que, embora o contêiner volte a rodar, você não está “dentro dele”.
Para poder assumir o controle da linha de comando (shell) dentro de um contêiner, use o comando ‘attach’ — seguido do seu ID ou do seu nome:

docker attach bdaaca495be7

Como rodar um contêiner em modo daemon

Além de usar estes contêineres interativos, que vimos até agora, podemos rodá-los nos bastidores do sistema ou daemonized.
Este modo é muito útil para executar tarefas (aplicações ou serviços) de longa duração em um contêiner.
Na verdade esta é a maneira mais comum de se executar Docker containers.
Veja um exemplo:

docker run --name primeiro_daemon -d ubuntu /bin/sh -c "while true; do echo ola mundo; sleep 1; done"
65f05aeced672283ba639aa240268d23fdaa7713ca0beb66da3f39155224502c

Aqui, usamos o comando ‘docker run’, em conjunto com o parâmetro ‘-d’ para enviar o contêiner pros bastidores ou background.
Dentro dele, escrevemos um pequeno script em loop, como comando.
O script envia a frase “ola mundo” uma vez a cada segundo, até ser interrompido — parando o contêiner ou o processo.
Com esta combinação de opções e parâmetros, é possível ver que o Docker não te coloca na shell de comandos. Ele apenas retorna a ID do contêiner o prompt linha de comando do seu terminal — enquanto o script continua a ser executado dentro do contêiner.
Como você já sabe, é possível acompanhar a execução dele, através do comando docker ps:

docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS               NAMES
65f05aeced67        debian:latest       /bin/sh -c 'while tr   29 minutes ago      Up 29 minutes                           primeiro_daemon     
bdaaca495be7        ubuntu:latest       /bin/bash              2 hours ago         Up 43 minutes                           jolly_meitner

Como ver o que acontece dentro de um contêiner

É possível, a qualquer momento, obter informações sobre processos que estejam em execução dentro de contêineres.
Neste momento, há um com um laço while em execução.
Você pode usar o comando ‘logs’ do Docker para obter informações sobre o andamento da execução dos processos:

docker logs primeiro_daemon
ola mundo
ola mundo
ola mundo

...

O comando ‘logs’ vai mostrar o resultado do script em execução no contêiner.
Se você deseja monitorar os resultados de alguma tarefa, acrescente a opção ‘-f’ — e observe os resultados como se estivesse executando o tails:

docker logs -f primeiro_daemon

Acrescente a opção ‘-t’ para exibir o timestamp ao lado de cada resultado:

docker logs -tf primeiro_daemon
[Sep 14 17:40:59.675] ola mundo
[Sep 14 17:41:00.676] ola mundo
[Sep 14 17:41:01.677] ola mundo
[Sep 14 17:41:02.678] ola mundo
[Sep 14 17:41:03.680] ola mundo
[Sep 14 17:41:04.681] ola mundo
[Sep 14 17:41:05.683] ola mundo
[Sep 14 17:41:06.684] ola mundo
[Sep 14 17:41:07.686] ola mundo
[Sep 14 17:41:08.688] ola mundo

Para concluir o monitoramento, pressione Ctrl + C.
Uma outra forma de observar o trabalho dentro do contêiner é rodando o comando ‘top’ com o Docker:

docker top primeiro_daemon
[/false]

UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                13434               2306                0                   14:37               ?                   00:00:00            /bin/sh -c while true; do echo ola mundo; sleep 1; done
root                16200               13434               0                   15:13               ?                   00:00:00            sleep 1

Também é possível ver estatísticas de funcionamento dos processos, através do comando ‘stats’:

sudo docker stats primeiro_daemon
CONTAINER           CPU %               MEM USAGE/LIMIT     MEM %               NET I/O
primeiro_daemon     0.12%               557.1 kB/8.059 GB   0.01%               10.47 kB/648 B

Para sair do painel de estatísticas, use a combinação Ctrl + C.

Como iniciar outro processo dentro de um contêiner existente

Um contêiner preexistente e com processos em andamento, pode aceitar novas tarefas e/ou novos processos, sem problemas — esta também é uma situação muito comum.
O comando ‘exec’ do Docker é que permite acrescentar tarefas aos contêineres.
Estas tarefas podem ser de 2 tipos: de background ou interativas.
As primeiras são inseridas para execução no contêiner e não tem interação depois disto.
As tarefas interativas são interessantes, pois permitem abrir uma shell dentro do contêiner.
Veja, no exemplo, como executar um comando em segundo plano dentro de um contêiner:

docker exec -d primeiro_daemon touch /etc/new_config_file

Aqui, o ‘-d’ sinaliza um processo rodando em segundo plano.
Em seguida, especificamos o nome do contêiner que queremos atingir e o comando que irá ser executado dentro dele.
Neste caso, o comando touch cria um arquivo vazio ‘new_config_file’ dentro do diretório ‘/etc’ dentro do contêiner ‘primeiro_daemon’.
Desta forma é que podemos usar o comando ‘exec’ do Docker para rodar comandos de manutenção, aplicativos de monitoramento, gerenciar tarefas etc. tudo dentro dos contêineres.
Com este método, é possível alterar a execução de um script on the fly.
Se você deseja obter acesso a uma shell dentro do contêiner, use as opções ‘-t’ e ‘-i’ (que já foram vistas anteriormente).
Com estas opções, é possível criar um terminal TTY e capturar o STDIN (standard input) dos processos em execução.
Além disto, é preciso indicar qual o contêiner e qual programa de shell a ser usado. Veja o exemplo:

docker exec -t -i primeiro_daemon /bin/bash

root@a3f74bdffacc:/# echo "Olha mae! Estou dentro do container!"

Olha mae! Estou dentro do container!

root@a3f74bdffacc:/# exit

Como você pode ver, o comando acima, criou uma nova sessão bash — dentro da qual é possível dar comandos dentro do contêiner.
No nosso exemplo, não esqueça, existe um processo rodando em background, ainda, dentro do ‘primeiro_daemon’.

Como reiniciar automaticamente um contêiner

Se um contêiner parar ou for finalizado em consequência de um erro, é possível configurar o Docker para pô-lo pra rodar de novo automaticamente — para o caso de você não “estar por perto”, isto pode ser útil.
Podemos sinalizar com ‘–restart’, quando queremos que haja um reinício e até quantas vezes tentar.
A flag ‘–restart’ aceita valores, como ‘always’ (sempre) e ‘on-failure’ (em caso de falhas).
Veja um exemplo:

docker run --restart=on-failure:3 --name segundo_daemon -d ubuntu /bin/sh -c "while true; do echo ola mundo; sleep 1; done"
4f505f038e885ef24f5b63474f28778f75d22098a4597d19dd92ff9cf1bb20d4

Neste exemplo, portanto, a configuração --restart=on-failure:3 pede para que ele tente retomar suas atividades, em caso de falha, (failure) até 3 vezes.

Como obter mais informações sobre um contêiner

O comando ‘inspect’ pode oferecer mais dados sobre um determinado contêiner.
Experimente:

docker inspect segundo_daemon

A saída deste comando pode ser muito extensa.
Para filtrar apenas a informação desejada, use o comando grep ou a opção ‘–format’ do docker:

docker inspect segundo_daemon | grep -A5 -i "state"
    "State": {
        "Running": true,
        "Paused": false,
        "Restarting": false,
        "OOMKilled": false,
        "Dead": false,

A opção ‘-A5’, do comando grep, diz para mostrar mais 5 linhas além daquela que contém a string ‘state’.
O que se vê é que o processo está rodando, não está pausado, não foi reiniciado, não foi alvo de um kill, não está morto…
Com a opção ‘–format’, é possível obter uma resposta mais objetiva para “o contêiner está em execução?”:

docker inspect --format='{{ .State.Running }}' segundo_daemon

true

Se quiser obter informações sobre as configurações de rede, use o template .NetworkSettings.IPAddress. No exemplo abaixo, mostro como obter informações de 2 contêineres (ou mais. Basta enfileirar os nomes):

docker inspect --format='{{ .NetworkSettings.IPAddress }}' segundo_daemon primeiro_daemon
172.17.0.2
172.17.0.3

Experimente explorar o diretório /var/lib/docker, onde se encontram imagens e configurações variadas de contêineres.
Já, os seus contêineres (que você criou) podem ser encontrados em /var/lib/docker/containers.

Como remover um contêiner

Ao terminar de usar um contêiner, é possível usar o Docker command ‘rm’ (remove).
É o mesmo comando que usamos para remover arquivos no UNIX ou no GNU/Linux.
Nas versões atuais do Docker, é possível remover um contêiner, ainda em execução, com o uso da opção ‘-f’ (force):

docker rm -f primeiro_daemon segundo_daemon
primeiro_daemon
segundo_daemon

Se quiser conferir a remoção, use o comando ‘ps’:

docker ps -a

No meu caso, não há mais nenhum contêiner rodando:

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

Este artigo abordou o uso básico do Docker, com comandos para iniciar instâncias do aplicativo e como manipular processos internamente. Vimos, ainda, como obter informações sobre os recipientes e, finalmente, como removê-los e onde encontrar mais imagens e configurações prontas, que podem ajudar a aprender mais sobre o assunto.
Há outros artigos sobre o uso do Docker neste site. Para encontrá-los, experimente usar a caixa de pesquisa.

Referências:
Google Books.
Slashroot.

Como limitar o uso de recursos dos usuários em sistemas UNIX e Linux com o comando ulimit

A limitação de recursos do sistema é a prática de regular até onde usuários, aplicativos e processos podem ir, com o objetivo de manter um equilíbrio entre a completa experiência de usuário e a segurança do sistema.
É bom frisar que, ainda que ajustados em função dos usuários, internamente, sua aplicação se refere aos processos.
Esta abordagem permite que usuários com limitações de recursos, possam rodar softwares que dependam de uma grande quantidade de processos abertos — ou seja, mesmo que um usuário esteja limitado a rodar no máximo 10 processos, ele pode fazer uso de aplicações gráficas (GNOME, KDE, Unity etc) que disparam, potencialmente, outras várias dezenas de processos.
Nos sistemas operacionais UNIX e GNU/Linux o comando ulimit é o que controla os limites dos recursos de sistema — tais como tamanho dos dados processos, quantidade de memória virtual que podem usar, quantidade máxima de arquivos que podem ser abertos etc.

Há diferenças entre um sistema e outro. O Solaris dá acesso ilimitado aos recursos ao root. No AIX, da IBM, há algumas limitações impostas ao superusuário.

Ao configurar os limites de recursos para um processo é importante saber que os limites que se aplicam são aqueles que afetam os processos pai e não os limites para o usuário que o está executando.
Por exemplo, o IBM Directory server roda sob uma conta de usuário ldap, criada no momento da instalação. Embora este serviço seja normalmente iniciado quando logado como root.
Se iniciado sob privilégios administrativos (root), quaisquer limites para o usuário ldap não afetam os processos pertencentes ao IBM Directory server. Para isto, o serviço precisa ser iniciado a partir da conta do usuário ldap.

Como obter informações sobre os limites de recursos de cada usuário

O comando ulimit pode configurar os recursos disponíveis “por usuário”, de maneira flexível (soft) ou rígida (hard).
Para ver todos os ajustes rígidos de recursos relativos ao usuário atual, use-o assim:

ulimit -Ha
core file size          (blocks, -c) unlimited
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 61250
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 4096
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) unlimited
cpu time               (seconds, -t) unlimited
max user processes              (-u) 61250
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

Para ver os ajustes flexíveis de recursos relativos ao usuário atual:

ulimit -Sa

As letras, entre parênteses, indicam a opção que pode ser dada ao ulimit para obter ou alterar aquela informação específica.

core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 61250
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 61250
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

Para ajudar a memorizar:

  • -H, de hard, se refere às opções rígidas.
  • -S, de soft, se refere às opções flexíveis.

Para exemplificar, se você quiser saber apenas qual a quantidade máxima de arquivos que podem ficar abertos, use ulimit -n.
O comando ulimit -a é equivalente a ulimit -Sa.
Você pode combinar parâmetros, para obter informações customizadas:

ulimit -v -x -u
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited
max user processes              (-u) 61250

No Linux, os ajustes rígidos de recursos são feitos pelo root para cada usuário, através do comando chuser.
Já os ajustes flexíveis, podem ser feitos pelo usuário comum livremente. Ou seja, desde que você não ultrapasse os valores estabelecidos pelos ajustes rígidos, pode alterá-los de acordo com suas necessidades.

Como alterar os limites flexíveis do usuário atual

Há momentos em que se deseja reduzir limites de recursos para a própria sessão — quando se deseja testar algum aplicativo, que se tenha dúvida quanto à qualidade de seu código.
Limitar fortemente a quantidade máxima de processos abertos simultaneamente por usuário (max user processes), pode prevenir que código malicioso de uma fork bomb, por exemplo, replique processos indefinidamente até derrubar o seu sistema.
Para aumentar a quantidade máxima de arquivos abertos open files, use o comando seguido da opção ‘-n’:

ulimit -n 2048

Seja parcimonioso(a) com a alteração dos limites flexíveis “para menos”. Valores muito baixos de alguns itens podem tornar o sistema impossível de operar.

Você vai encontrar valores diferentes de um UNIX para outro ou entre as várias distros GNU/Linux. Só para citar um exemplo desta variação, o Debian 8.1 32 bit (quando instalado via netinstall e sem ambiente gráfico), por padrão, usa o valor 841 no parâmetro max user processes. A versão 64 bit, usa o valor 7976.
Isto é normal.

Note, ainda que os valores alterados só valem para a sessão atual. Ao reiniciar o sistema, eles retornam à configuração anterior. Se você errar e ficar com um sistema inutilizável, basta reiniciar ou abrir um outro terminal.

Como configurar o arquivo /etc/security/limits

Para alterar e manter os valores, mesmo após reiniciar o sistema, faça as alterações dentro do arquivo /etc/security/limits.conf (nas distros derivadas do Debian).
O arquivo limits.conf deve ser configurado em 4 colunas:

Parâmetro Descrição
domain Esta coluna pode conter um nome de usuário (justincase, hkeitel) ou de um grupo (p/ex.: @grupo).
type Pode ser tanto soft, para flexível como hard, para rígido.
item Onde se incluem os parâmetros do sistema a configurar. Veja abaixo.
value Os valores podem ser dados em blocos (de 512 ou 1024 bytes, a depender do seu sistema operacional, do seu sistema de arquivos etc.

Os parâmetros de configuração, presentes no Ubuntu 14.04 e Debian 8.1, no arquivo /etc/security/limits.conf, são os seguintes:

  • core – limita o tamanho do arquivo de núcleo ou core file size ou core dump file size. Este arquivo é constituído por uma imagem da memória de um processo, quando ele é terminado abruptamente. Esta informação é geralmente usada por aplicativos depuradores (KB).
  • max data size. Limite do tamanho dos segmentos de dados dos processos (KB).
  • maximum filesize ou maior tamanho de arquivo permitido a um processo abrir (KB).
  • max locked-in-memory address space. O memlock é o tamanho máximo de memória física que pode ser retida por um processo.
    A função deste recurso é certificar que uma certa quantidade de informações esteja sempre armazenada na RAM e nunca movida para o swap — o que mantém a velocidade de acesso a esta informação (KB).
  • max number of open files ou maior quantidade de arquivos que podem ficar abertos simultaneamente (unidades).
  • rss – max resident set size. É a quantidade máxima de dados pertencentes a um processo, que pode ficar residente em memória RAM.
    Um dos motivos para limitar este item é impedir que uma única aplicação do seu sistema devore toda memória principal (RAM), o que forçaria todas as outras aplicações a usar o swap, que é extremamente lento. (KB)

Em computação, o RSS ou resident set size é a porção de memória ocupada por um processo, mantida na memória RAM.
O resto da memória ocupada vai estar no swap ou no sistema de arquivos — ou porque parte foi paginada ou por que partes do arquivo executável, ainda não foram carregadas.

  • stackmax stack size limita o tamanho do process stack ou pilha de processos em bytes. Ao atingir este limite, um sinal SIGSEV é gerado, obrigando o próximo processo a empregar uma nova pilha (KB).
  • cpumax CPU time representa o tempo dedicado pela CPU para processar instruções de um programa ou do sistema operacional (MIN).
  • nprocmax number of processes, delimita a quantidade máxima de processos para a sessão atual.
  • asaddress space limit, estabelece um teto para o tamanho máximo de um processo em memória virtual (KB).
  • maxloginsmax number of logins for this user ou maior número de autenticações no sistema permitido ao usuário atual.
  • maxsysloginsmax number of logins on the system ou maior número de autenticações no sistema permitido.
  • prioritythe priority to run user process with, designa a prioridade para rodar processos do usuário.
  • locksmax number of file locks the user can hold
  • sigpendingmax number of pending signals, especifica o limite de sinais que podem se enfileirar para atingir o ID do usuário, vindas de um determinado processo.
  • msgqueuemax memory used by POSIX message queues Estabelece a quantidade máxima (em bytes) de memória usada por filas de mensagens POSIX.
  • nicemax nice priority allowed to raise to values: [-20, 19]. Especifica um teto para aumentar o valor do nice, com o uso dos comandos setpriority e nice.
  • rtpriomax realtime priority, define um teto para a prioridade de tempo real para a sessão atual.
  • chrootchange root to directory, no Debian, permite informar um novo diretório para o superusuário.

Para que as alterações feitas ao arquivo /etc/security/limits.conf tenham efeito, é necessário reiniciar o sistema.
Para alterar individualmente os limites de usuários, use o comando chuser. Neste caso, basta fechar a sessão e se autenticar de novo para estar sob os efeitos dos novos ajustes.
Para finalizar o assunto, segue um exemplo de uso do comando para alterar o resident set size do usuário salsicha:

chuser rss=-1 salsicha

Referências:
Página do Manual do Linux: http://www.man-page.net/2/setrlimit.
The core dump file: http://man7.org/linux/man-pages/man5/core.5.html.

Entenda como uma CPU física é dividida em múltiplas unidades virtuais

Qualquer que seja a sua ferramenta de virtualização, na hora de criar uma máquina virtual, com suas especificações e características de hardware, já se perguntou sobre o número ideal de CPUs virtuais a ser alocado a esta máquina virtual (VM)?
Já se preocupou com as limitações que este número impõe à quantidade de VMs no servidor?
Uma CPU física é simplesmente um núcleo (core) dentro do conjunto todo de componentes que formam um processador, com o suporte a execução de múltiplas threads desligado.

A Tecnologia Hyper-Threading, da Intel, permite que múltiplos encadeamentos sejam executados em cada núcleo.

Isto significa que um processador Xeon, com seis núcleos e multithreading desligado, irá oferecer o trabalho de seis CPUs fisicamente existentes.
Se o suporte ao multithreading (multi encadeamento) estiver ativado, o servidor irá ver cada um destes encadeamentos como uma CPU física. Assim, se cada um destes 6 núcleos tiver suporte a 2 encadeamentos, o servidor enxergará 12 CPUs físicas.
Quando um hyperviser ou monitor de máquina virtual se encontra instalado, cada CPU física é abstraída em CPUs virtuais, o que basicamente consiste em dividir os ciclos disponíveis para cada núcleo — o que, por sua vez, permite a múltiplas VMs compartilhar o tempo de um dado núcleo de processador físico.
Comumente, o monitor de VMs designa uma carga de trabalho por CPU virtual (por núcleo).
Via de regra, cada CPU virtual pode suportar uma quantidade variável entre 4 e 8 máquinas virtuais.
Vamos supor limites muito prudentes de 4 VMs por CPU virtual.
Se o servidor oferece 2 processadores quad-core (o que dá um total de 8), a expectativa é de que tem o potencial de suportar 32 VMs (8 X 4 = 32).
No caso de ter 4 processadores quad-core instalados (num total de 16 núcleos), o potencial do servidor sobe para 64 VMs (16 X 4 = 64) ou mais.
Convém lembrar sempre, quanto maior a quantidade de VMs a compartilhar uma CPU virtual, menor é a quantidade de ciclos disponíveis para cada uma destas máquinas virtuais e mais penalizadas serão suas performances.
Se a carga de trabalho em um servidor precisar de mais ciclos de CPU, é melhor implementar menos máquinas virtuais por CPU.
Adicionalmente, você pode alocar várias CPUs virtuais a uma VM — só não exceda o número de processadores físicos presentes no servidor.
Se o servidor, por exemplo, tem apenas um soquete de processador, designe apenas uma CPU virtual por VM.
Havendo 2 processadores por soquete, atribua no máximo duas CPUs virtuais a cada VM e assim, por diante.

Referências:
http://www.intel.com.br/content/www/br/pt/architecture-and-technology/hyper-threading/hyper-threading-technology.html
http://searchservervirtualization.techtarget.com/answer/How-is-a-physical-CPU-divided-into-multiple-virtual-CPUs?utm_medium=EM&asrc=EM_ERU_46872330&utm_campaign=20150828_ERU%20Transmission%20for%2008/28/2015%20%28UserUniverse:%201705526%29_myka-reports@techtarget.com&utm_source=ERU&src=5420646

Como instalar e começar a usar o Docker no Ubuntu

O Docker pode ser instalado nos sistemas operacionais baseados no Debian GNU/Linux (o Ubuntu é um deles) via apt-get, como irei mostrar abaixo.
Nos meus exemplos, fiz uso do Ubuntu 14.04 LTS e do Debian 8.1 — ou seja, eles valem para todas as outras distros que sejam baseadas neles.
Nunca é demais lembrar que o Docker, no momento, só suporta as versões 64 bits destes sistemas operacionais.
Note que abordei a instalação das versões estáveis e instáveis (nightly build) do Docker em tópicos separados, neste post.

Como instalar o Docker no Ubuntu ou no Debian

Há basicamente 2 métodos de instalação (escolha o que for melhor pra você). No primeiro, vamos buscar o aplicativo dos repositórios oficiais da sua distro e no segundo vamos buscá-los do site.
A diferença é que no primeiro você estará optando por um versão não tão nova, porém mais estável. No segundo método, a opção é por uma versão mais atual.

sudo apt-get update
sudo apt-get install docker.io

Para saber a versão instalada, use a opção ‘–version’:

docker --version
Docker version 1.0.1, build 990021a

O outro método baixa e executa automaticamente o script de instalação direto do site do Docker: https://get.docker.com.
Esta solução necessita do aplicativo wget e pode ser usada para instalar uma versão mais atualizada do Docker.
Veja como:

sudo wget -qO- https://get.docker.com/ | sh

Aguarde um pouco e veja o script fazer o seu trabalho.
Ao final, verifique a versão instalada do aplicativo:

sudo docker --version
Docker version 1.8.1, build d12ea79

Para testar a instalação, execute o seguinte procedimento:

docker run hello-world

O resultado deve ser aproximadamente o que segue:

Unable to find image 'hello-world' locally
Pulling repository hello-world
af340544ed62: Download complete 
535020c3e8ad: Download complete 

Hello from Docker.
This message shows that your installation appears to be working correctly.

De acordo com a documentação oficial, a mensagem Hello from Docker (em destaque) indica que a instalação aparenta estar funcionando corretamente.
Para gerar esta mensagem, o cliente Docker passou pelas seguintes etapas:

  1. Contatou o daemon docker;
  2. O daemon trouxe a imagem “hello world” do Docker Hub;
  3. Em seguida, criou um novo contêiner a partir daquela imagem, que roda o executável que produz a mensagem, que foi destacada acima;
  4. O Docker daemon transmitiu a mensagem ao cliente, que a reenviou para o seu terminal.

Como instalar a versão experimental do Docker

Neste ponto estamos saindo da zona segura. Software em estágio experimental não deve ser executado em máquinas de produção.
O uso da versão em desenvolvimento de um aplicativo é interessante para quem deseja ter acesso aos últimos recursos que os programadores inseriram e não se importa de viver perigosamente.
Espere encontrar problemas ao usá-la.

Ao usar a versão nightly build de um software e relatar corretamente os problemas que você encontrou, você ajuda a equipe de desenvolvimento a melhorar o seu programa preferido.
Além disso, vocẽ fica fazendo parte do grupo de usuários que pode ter um primeiro contato com os novos recursos do software.

Se vocẽ quiser usar a última versão (nightly build) do Docker, no Ubuntu, rode o script a partir do site https://experimental.docker.com/.
Segue o procedimento:

wget -qO- https://experimental.docker.com/ | sh
docker --version
Docker version 1.9.0-dev, build 5dadfa8, experimental

A sua versão provavelmente será mais nova que a deste texto.
Se você quer usar a versão experimental e não quer desinstalar a versão estável, sugiro baixar diretamente o binário da nightly build version do Docker. O procedimento é o que segue:

wget https://experimental.docker.com/builds/Linux/x86_64/docker-latest
chmod +x docker-latest
./docker-latest version
/docker-latest version
Client:
 Version:      1.9.0-dev
 API version:  1.21
 Go version:   go1.4.2
 Git commit:   5dadfa8
 Built:        Wed Aug 26 17:33:57 UTC 2015
 OS/Arch:      linux/amd64
 Experimental: true

Configure o Docker para seu usuário

Como você já deve ter percebido, o Docker é executado através do sudo — com privilégios administrativos, portanto.
Para executá-lo sem isso, como usuário normal, faça o seguinte procedimento:

sudo usermod -aG docker justincase

No seu caso, substitua justincase pelo seu nome de usuário.

Como montar um bom sistema de relatório de erros (log system) em PHP.

Sites e aplicativos com um grande número de usuários e que tratam de informações sensíveis, costumam dispor de muitos recursos.
À medida em que os desenvolvedores trabalham para adicionar estes recursos, pontos fracos podem também estar sendo criados e, naturalmente, os bugs não resolvidos (ou não percebidos) podem trazer riscos de segurança.
Se algo errado estiver ocorrendo no uso do seu sistema, você gostaria de ser o primeiro a saber, não é?
O código, que segue, provê um gestor de logs que ajuda a rastrear erros e comportamentos indesejados.
Se quiser se aprofundar no assunto, não esqueça de dar uma olhada nos links ao final do texto.

O que você precisa ter

Um servidor web completo já dispõe de arquivos de log do Apache, do MySQL, entre outros.
Alguns destes arquivos de log, contudo, só podem ser acessados pelo administrador.
Este é o principal motivo para criar o seu próprio sistema de relatório de ocorrências (Logging) — onde você possa ter acesso às informações de que precisa.

A documentação oficial do PHP encoraja fortemente os desenvolvedores a criar sistemas de tratamento de erros em vez de permitir a exibição padrão de erros nos sites.

O primeiro requisito do seu sistema de logging é ser à prova de erros — ou então você voltará a ficar dependente do sistema de erros do servidor.
O sistema, aqui descrito, lida com os erros e avisos (warnings) do PHP, do MySQL e das notificações geradas pelo próprio desenvolvedor.
Erros relacionados especificamente ao servidor (como o htaccess) estão fora do escopo deste sistema.
Veja outras características do sistema de relatórios de ocorrências:

  • O sistema de logging precisa acompanhar e relatar os eventos definidos pelo desenvolvedor. Precisa enviar avisos sobre vulnerabilidades em potencial.
    Precisa, ainda, interceptar e relatar erros e avisos recebidos do compilador, do servidor de banco de dados e, claro, erros disparados pelo código no lado do servidor.
  • Se houver pane no sistema de armazenagem do servidor ou problemas no banco de dados, ainda queremos ter acesso aos logs.
    Para isto, o sistema enviará os relatório para o email indicado pelo desenvolvedor.
    Caso o servidor de email esteja “fora do ar” também, um arquivo de log será usado para guardar as informações.
    Garantir o acesso aos arquivos de log de qualquer maneira é um dos objetivos do sistema.
  • É importante que usuários não veja mensagens de erro ou de aviso vindas do sistema, não só por uma questão de estética, mas por segurança (por obscuridade, eu sei).
    Obviamente, os usuários precisam de uma explicação do sistema sobre por que “não estão conseguindo” fazer alguma coisa ou por que algo não esta funcionando.
    Se for o caso de informar ao usuário que houve um erro no sistema, que seja em uma página à parte, bonita, dizendo que há técnicos cuidando do problema.

Como o sistema irá funcionar

Diante do que foi exposto, até aqui, o sistema de logging vai funcionar assim:

  1. No construtor da classe, o arquivo de configuração é lido e as tabelas necessárias serão criadas.
    O método preferencial de armazenamento é o banco de dados.
    O sistema verifica se há uma conexão ao servidor MySQL e se existe uma tabela.
    Se a conexão não for possível, um log de erro é enviado por email ou gravado em um arquivo.
    Se a tabela não existir, o sistema cria uma, desde que haja conexão ao banco de dados.
  2. Baseado em estarmos no modo de produção ou de desenvolvimento, as opções de relatar erros são ajustadas no compilador PHP.
    No modo de produção, erros não são exibidos ao usuário — mas apenas uma página simpática com uma mensagem genérica de erro.
    Os erros e as exceções oriundos do PHP e do MySQL são enviados para uma função, que recolherá todos os detalhes necessários e os guardará em um log para analisado posteriormente pelos desenvolvedores.
  3. No modo de desenvolvimento, o PHP se torna mais verboso acerca de erros e avisos do compilador.
    Este ajuste é feito no arquivo de configuração php.ini.

  4. Além disto, há funções essenciais adicionadas ao código, para enviar erros, avisos e notificações em forma de relatórios.
    Todas elas têm opções que permitem especificar qual o método padrão de gravação dos dados — no banco de dados, no email ou em arquivo. A gravação de um arquivo em disco é a última opção, usada quando as outras falharem.
    Quando erros críticos são relatados, a execução do script para para exibir aquela página de erro padrão.
  5. No modo de desenvolvimento do PHP, erros são direcionados para exibição na página, usando uma fonte mais bonita.
    Não são salvos no banco de dados (apenas as notificações e os avisos).
  6. Fora o conceito, não há distinção entre avisos e notificações.
    Avisos são usados para guardar mensagens importantes. Notificações são apenas informações.

Requisições para desenvolvimento

Antes de botar a mão na massa certifique-se de que seu sistema tenha alguns recursos básicos.
Todo o código foi testado em uma máquina Ubuntu 14.04 LTS, mas você pode usar qualquer outro sistema operacional da sua preferência.
Veja outras requisitos:

  • Você precisa ter à disposição um servidor web — o Apache é recomendado, mas não é obrigatório.
  • O código está escrito, essencialmente, em PHP. Você precista ter um servidor rodando um compilador/interpretador PHP, portanto.
  • O banco de dados MySQL com o driver PDO instalado também é requerido.

Para resumir, um servidor LAMP, resolve perfeitamente a situação.

Os arquivos do sistema

O script é composto de, basicamente, 4 módulos. Veja quais sao:

  • logconf.php — Este é o arquivo de configuração do sistema. Nele estarão presentes as informações de conexão ao banco de dados, de envio de e-mails e de armazenamento de dados no disco.
  • logsystem.php — Este arquivo contém a classe do sistema de logging. Você provavelmente não irá precisar de múltiplas instâncias dele.
  • error500.html — Este é um arquivo exemplo do que poderia ser a página informativa de que houve erro, ao usuário. Se você decidir alterar seu nome, não se esqueça de mudar a referência a ele, lá no arquivo de configuração.
  • index.php — Este também é um arquivo exemplo, que mostra o funcionamento do sistema de logging. Baseie-se nele para integrar este script ao resto do seu sistema.

O código

Comecemos pelo arquivo de configuração. Faça todos os ajustes com atenção para integrá-lo às configurações do seu servidor.

logconf.php

<?php

/**

 * Configurações de log

 */



 // Opções de relato de erros

 define('ERROR_REPORTIONG', E_ALL | E_STRICT | E_NOTICE | E_WARNING);

 // Página template de erro

 define('ERROR_TEMPLATE', 'error500.html');



 // Dados de configuração do SEU servidor de banco de dados

 define('DB_SERVER', 'localhost');

 // username do banco de dados

 define('DB_USER', 'root');

 // Senha do usuário

 define('DB_PASSWORD', 'root');

 // Nome do banco de dados

 define('DB_NAME', 'logsdb');

 
 // Endereço de e-mail do administrador.

 define('ADMIN_MAIL', 'admin@meusite.com.br');

 // No-reply sender e-mail ou e-mail do remetente,
 // para o administrador saber de onde estão vindo as mensagens

 define('NOREPLY_MAIL', 'noreply@meusite.com.br');


 // Pasta aonde serão salvos os logs

 define('LOGS_FOLDER', 'logs');

logsystem.php

<?php
/**

 * Sistema de logging

 */



 // Inclui os dados do arquivo de configuração

 require_once "logconf.php";

 

 /**

  * Corpo do sistema de logging

  */

 class LogSystem

 {

    /**

     * Guarda a conexão ao banco de dados

     */

    private $_db = null;



    /**

     * Armazena os erros do logger

     */

    private $_error = '';



    /**

     * Modo de execução

     */

    private $_mode = 'development';



    /**

     * Inicializa o sistema de logging

     */

    public function __construct($mode = 'development')

    {

        // Modo de gravação

        $this -> _mode = $mode;

        // Cria a conexão ao banco de dados

        if (DB_SERVER && DB_USER && DB_NAME) {

            // Configuração

            $dsn = 'mysql:host='.DB_SERVER.';dbname='.DB_NAME;

            // Conexão

            try {

                $this->_db = new PDO(

                    $dsn,

                    DB_USER,

                    DB_PASSWORD ? DB_PASSWORD : ''

                );

                // Configuração do conjunto de caracteres (characters set)

                $this -> _db -> exec("SET NAMES 'utf8'");

                // Exibe os erros do MySQL

                $this -> _db -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

                // Cria as tabelas padrão do sistema, caso ainda não existamCreate default tables, if not existing

                $this -> _db -> exec(

                    "CREATE TABLE IF NOT EXISTS `logs` (

                      `id` int(11) NOT NULL AUTO_INCREMENT,

                      `type` smallint(6) NOT NULL DEFAULT '0',

                      `title` varchar(128) NOT NULL,

                      `message` varchar(256) DEFAULT NULL,

                      `details` text,

                      `date` datetime NOT NULL,

                      PRIMARY KEY (`id`)

                    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ;

                    "

                );

            } catch (PDOException $e) {

                // Grava erro do logger

                $this -> _exceptionHandler($e);

            }

        }



        // Registra uma função para ser executada no desligamento

        register_shutdown_function(array($this, 'fatalErrorHandler'));

        // Configura o manipulador de erros (error handler)

        set_error_handler(array($this, 'errorHandler'));

        // Configura o manipulador de exceções 

        set_exception_handler(array($this, '_exceptionHandler'));

        // Exibe os erros dentro do navegador, no modo de desenvolvimento

        if ($this -> _mode == 'development') {

            ini_set("display_errors", "on");

        }

        // Relata todos os erros

        error_reporting(ERROR_REPORTIONG ? ERROR_REPORTIONG : E_ALL);

    }



    /**

     * Loga os erros do PHP apropriadamente

     *

     * @param string $title    Título do ero

     * @param string $message  Mensagem descritiva curta

     * @param string $details  Detalhes técnicos avançados

     * @param bool   $critical O script pára aqui

     *

     * @return null

     */

    public function logError($title, $message, $details, $critical = false)

    {

        // Save log (error or critical error)

        $this->_saveLog($title, $message, $details, ($critical ? 3 : 2));

        // Se encontrar erro crítico, pára o script

        if ($critical) {

            // Remove cabeçalho (header) preexistente 

            header_remove();

            // Cria cabeçalho

            header('HTTP/1.0 500 Internal Server Error');

            // No modo de desenvolvimento, exibe erros no navegador

            if ($this -> _mode == 'development') {

                print "<!doctype html><html><head>";

                print "<link href='http://fonts.googleapis.com/css?family=Lato' ";

                print "rel='stylesheet' type='text/css'>";

                print "<style>body{font-family:'Lato',Arial;}";

                print "table{width:80%;border-collapse:collapse;padding:10px;}";

                print "th {padding:10px;}td{color: #888;padding:10px;}</style>";

                print "</head><body>";

                // Mostra o erro em uma bela caixa

                print "<div style='text-align: center;'>";

                print "<h1 style='color: #ff4800;'>".$title."</h1>";

                print "<p>".$message.'</p><hr>';

                print "<p>".$details.'</p>';

                print "</div>";



                // Fecha as tags BODY e HTML

                print "</body></html>";

            } else {

                // Se o template estiver definido e presente, exibe

                if (ERROR_TEMPLATE && @file_exists(ERROR_TEMPLATE)) {

                    include_once(ERROR_TEMPLATE);

                }

            }

            // Termina

            die();

        }

    }



    /**

     * Loga os avisos do PHP

     *

     * @param string $title    Título do aviso

     * @param string $message  Curta mensagem descritiva

     * @param string $details  Detalhes técnicos avançados

     *

     * @return null

     */

    public function logWarning($title, $message, $details, $db = true, $mail = true)

    {

        // Grava o log (erro ou erro crítico)

        $this -> _saveLog($title, $message, $details, 1, $db, $mail);

    }



    /**

     * Loga as notificações do PHP

     *

     * @param string $title    Título da notificação

     * @param string $message  Mensagem descritiva curta

     * @param string $details  Detalhes técnicos avançados

     *

     * @return null

     */

    public function logNotice($title, $message, $details, $db = true, $mail = true)

    {

        // Grava o log (erro ou erro crítico)

        $this -> _saveLog($title, $message, $details, 0, $db, $mail);

    }



    /**

     * Esta função irá gravar um log sempre que puder

     *

     * @param string $title   Título de erro

     * @param string $message Mensagem descritiva curta

     * @param string $details Detalhes técnicos avançados

     * @param bool   $nodb    Não armazena no banco de dados

     * @param bool   $nomail  Não envia por email

     *

     * @return null

     */

    private function _saveLog($title, $message, $details, $type = 0, $db = true, $mail = true)

    {

        // No modo de desenvolvimento, os erros são exibidos no navegador. Sem necessidade de gravar, portanto

        if ($this -> _mode == 'development' && $type > 1) return;

        // Permite saber se o log foi gravado em algum lugar

        $saved = false;

        // Tenta gravar no banco de dados, caso a conexão se concretize

        if ($this -> _db && $db) {

            try {

                // Prepara a declaração SQL

                $sth = $this -> _db -> prepare(

                    "INSERT INTO logs (type, title, message, details, date) ".

                    "VALUES (:type, :title, :message, :details, :date)"

                );

                // Send the parameters

                $sth -> execute(

                    array(

                        ':type' => $type,

                        ':title' => $title,

                        ':message' => $message,

                        ':details' => $details,

                        ':date' => date('Y-m-d H:i:s')

                    )

                );

                // Informa que o log está salvo

                $saved = true;

            } catch (PDOException $e) {

                // Se o log não pode ser salvo, envia erro via email

                $this -> _saveLog(

                    'O log n&atilde;o p&ocirc;de ser gravado no banco de dados',

                    'Um log ('.$title.') n&atilde;o p&ocirc;de ser gravado no banco de dados, como requisitado',

                    'Exce&ccedil;&atilde;o: '.$e->getMessage(),

                    3,

                    true

                );

            }

        }



        // Se não foi gravado no banco de dados, tenta enviar por email

        if (!$saved && $mail && ADMIN_MAIL) {

            // Compõe a mensagem completa

            $fullmessage = $message."rn".$details;

            // Cabeçalhos

            $headers = array(

                'From: LoggingSystem <'.NOREPLY_MAIL.'>',

                'To: Administrator <'.ADMIN_MAIL.'>',

                'Subject: '.$title,

                'X-Mailer: PHP/'.phpversion()

            );

            // Tenta mandar e-mail

            if (mail(ADMIN_MAIL, $title, $fullmessage, implode("\r\n", $headers))) {

                // Log enviado

                $saved = true;

            }

        }



        // Como última opção, grava o log em um arquivo texto.

        if (!$saved && LOGS_FOLDER) {

            // Se certifica de que o arquivo de log existe

            if (!file_exists(LOGS_FOLDER)) {

                // ... se não existir, cria um

                mkdir(LOGS_FOLDER, 0777);

            }

            // Cria um arquivo HTACCESS para restringir acesso ao público

            if (!file_exists(LOGS_FOLDER.'/.htaccess')) {

                $htaccess = "Order deny,allowrndeny from all";

                file_put_contents(

                    LOGS_FOLDER.'/.htaccess',

                    $htaccess

                );

            }

            // Agora, grava o arquivo dentro da pasta segura

            @file_put_contents(

                LOGS_FOLDER.'/log_'.date('d_m_Y_H_i_s').'.txt',

                $title."\r\n\r\n".$message."\r\n\r\n".$details

            );

        }

    }



    /**

     * Verifica existência de erros fatais e os relata

     *

     * @return null

     */

    public function fatalErrorHandler()

    {

        // Pega o erro

        $error = error_get_last();

        if ($error) {

            // Chama o manipulador de erros

            $this -> errorHandler(

                $error['type'],

                $error['message'],

                $error['file'],

                $error['line']

            );

        }

    }

    

    /**

     * Manipulador de erros para aqueles gerados pelo compilador

     *

     * @param int    $errno Error level

     * @param string $str   Error message

     * @param string $file  Error file

     * @param int    $line  Error line

     * 

     */

    public function errorHandler($errno, $str, $file, $line, $context = null)

    {

        // Cria nova exceção

        $this -> _exceptionHandler(new ErrorException($str, 0, $errno, $file, $line));

    }



    /**

     * Manipulador de exceções para erros gerados pelo compilador

     *

     * @param Exception $e Exception object

     * 

     */

    private function _exceptionHandler($e)

    {

        // Título do erro

        $error_title = 'C&oacute;digo de erro fatal';

        // Mensagem de erro

        $error_message = $e -> getMessage();

        // Detalhes do erro

        $error_details = 

            'Type: '.get_class($e).

            ', File: '.$e -> getFile().

            ', Line: '.$e -> getLine();

        // Chama o logger de erro público e pára tudo

        $this -> logError(

            $error_title,

            $error_message,

            $error_details,

            true

        );

    }

 }

index.php

Abaixo, segue o arquivo index.php.
Observe que ele inclui a classe LogSystem e que criar o objeto é suficiente para iniciar todo o processo.

<?php



/**

 * Index

 */



 // Inclui o sistema de logging

 include "logsystem.php";



 // Cria e fica pronto

 $LogSystem = new LogSystem('production');



 //

 // Aqui você insere o SEU CÓDIGO

 //

Enfim, este é o código.
Eu o testei e ele tem funcionado para mim.
Se você encontrar algum bug ou tiver alguma ideia melhor, compartilhe com outros leitores na sessão de comentários.
Do jeito que o sistema se encontra, ele não deixa passar nenhuma mensagem de erro sem o devido tratamento e sem ser gravada para análise posterior.

Referências:
Código original em http://coding.bmain.net/tutorials/php/advanced_logging_system_in_php_for_careful_developers.
Como instalar um servidor Linux, Apache, MySQL e PHP: http://elias.praciano.com/2011/11/como-instalar-apache-mysql-e-php-no-linux/.
Manual do PHP (o arquivo php.ini): http://php.net/manual/pt_BR/errorfunc.configuration.php.