Pré-história
Um belo dia, eu precisava implantar um ambiente de desenvolvimento para o meu projeto. O Vagrant já estava cansado disso e queria ter um ambiente de desenvolvimento único para todos os participantes do projeto que fosse idêntico ao servidor de produção. Assim, depois de ouvir informações sobre o docker hipster, decidi começar a lidar com isso. A seguir, tentarei descrever com o máximo de detalhes possível todas as etapas, desde a instalação de uma janela de encaixe em uma LAN até a implantação de um produto no KVM.
Pilha de tecnologia original:- janela de encaixe
- symfony 4
- nginx
- php-fpm
- postgresql
- elasticsearch
- rabbitmq
- jenkins
Ferro:- laptop no sistema operacional Ubuntu 16.04
- servidor de produção na hospedagem KVM
Por que, além da pilha tecnológica, também listei a pilha de ferro?Se você nunca trabalhou anteriormente com uma janela de encaixe, poderá encontrar vários problemas relacionados especificamente ao hardware, ao sistema operacional do seu laptop ou ao tipo de virtualização de hospedagem.
O primeiro e provavelmente o aspecto mais importante ao começar a trabalhar com a janela de encaixe é o sistema operacional do seu laptop. A maneira mais fácil de trabalhar com o docker é em sistemas Linux. Se você trabalha no Windows ou Mac, terá 100% de algumas dificuldades, mas essas dificuldades não serão críticas e se você quiser “pesquisar” como isso é corrigido, não haverá problemas.
A segunda pergunta é de hospedagem. Por que a hospedagem é necessária com o tipo de virtualização KVM? O motivo é que a virtualização VPS é muito diferente da KVM e você simplesmente não poderá instalar a janela de encaixe no VPS, pois o VPS aloca dinamicamente os recursos do servidor.
Subtotal: para o início mais rápido na janela de encaixe, é mais razoável escolher o Ubuntu como um SO local e hospedagem KVM (ou seu próprio servidor). Além disso, a história continuará precisando desses dois componentes.
Docker-compor para LAN
Instalação
Primeiro, você precisa instalar a própria janela de encaixe localmente. Você pode ver as instruções de instalação no
link do site oficial
para a documentação oficial do ubuntu (você precisa instalar o docker e o docker-compose) ou executando o comando no console:
curl -sSl https://get.docker.com/ | sh
Este comando instalará a janela de encaixe e a composição de encaixe. Depois disso, verifique a versão do Docker com o comando:
docker --version
Estou começando tudo isso na janela de encaixe versão 18.06.0-ce.
A instalação está completa!
Consciência
Para trabalhar com algo com menos sucesso - você precisa ter uma idéia de como ele funciona. Se você trabalhou anteriormente apenas com o Vagrant ou algo semelhante, será extremamente incomum e incompreensível a princípio, mas isso é apenas a princípio.
Vou tentar fazer uma analogia para o Vagrant. Agora muitos podem dizer que comparar Vagrant e Docker está fundamentalmente errado. Sim, concordo com isso, mas não vou compará-los, apenas tentarei transmitir aos recém-chegados que trabalharam apenas com o sistema de trabalho Vagrant the Docker, apelando para o que os recém-chegados sabem.
Minha visão do contêiner "nos dedos" é a seguinte: cada contêiner é um pequeno mundo isolado. Cada contêiner pode ser imaginado como se fosse um pequeno Vagrant no qual apenas uma ferramenta está instalada, por exemplo, nginx ou php. Inicialmente, os contêineres geralmente são isolados de tudo ao seu redor, mas com manipulações complicadas, você pode configurar tudo para que eles se comuniquem e trabalhem juntos. Isso não significa que cada um dos contêineres seja uma máquina virtual separada, nem um pouco. Mas é mais fácil para o entendimento inicial, como me parece.
O Vagrant simplesmente retira parte dos recursos do seu computador, cria uma máquina virtual, instala um sistema operacional, instala bibliotecas, instala tudo o que você escreveu no script depois de vagar. Por fim, parece algo como isto:
→
Ver esquemaO Docker, por sua vez, funciona radicalmente diferente. Não cria máquinas virtuais. O Docker cria contêineres (por enquanto, você pode considerá-los máquinas micro-virtuais) com seu sistema operacional Alpine e 1-3 bibliotecas necessárias para o aplicativo funcionar, por exemplo, php ou nginx. Ao mesmo tempo, o Docker não bloqueia os recursos do seu sistema, mas simplesmente os utiliza conforme necessário. Por fim, para ilustrar, será algo parecido com isto:
→
Ver esquemaCada um dos contêineres tem uma imagem a partir da qual é criado. A grande maioria das imagens é uma extensão de outra imagem, por exemplo, Ubuntu xenial ou Alpine ou Debian, na qual drivers adicionais e outros componentes são colocados no topo.
Minha primeira imagem foi para php-fpm. Minha imagem estende a imagem oficial do php: 7.2-fpm-alpine3.6. Ou seja, em essência, ele pega a imagem oficial e fornece os componentes que eu preciso, por exemplo, pdo_pgsql, imagick, zip e assim por diante. Assim, você pode criar a imagem que precisa. Se você quiser, você pode usá-lo
aqui .
Com a criação das imagens, tudo é bastante simples, na minha opinião, se elas são feitas com base no xenial, por exemplo, mas elas produzem um pouco de hemorróidas se forem feitas com base no Alpine. Antes de começar a trabalhar com o docker, eu basicamente não ouvia falar do Alpine, pois o Vagrant sempre trabalhava para mim no Ubuntu xenial. Alpine é um sistema operacional Linux vazio, no qual não há praticamente nada (mínimo extremo). Portanto, no início, é extremamente inconveniente trabalhar com ele, já que existe, por exemplo, a mesma instalação do apt-get (com a qual você se acostuma), mas há apenas o apk add e um conjunto de pacotes não muito sensato. Uma grande vantagem do Alpine é seu peso, por exemplo, se Xenial pesa (abstratamente) 500 sacas, então Alpine (abstratamente) é de cerca de 78 sacas. O que isso afeta? E isso afeta a velocidade de criação e o peso final de todas as imagens que serão armazenadas no servidor no final. Digamos que você tenha 5 contêineres diferentes e tudo baseado em xenial, seu peso total será superior a 2,5 GB e alpino - cerca de 500 sacas. Portanto, idealmente, devemos nos esforçar para garantir que os contêineres sejam o mais fino possível. (Link útil para instalar pacotes nos pacotes Alpine -
Alpine ).
Em todos os lugares no
hub do
docker, eles escrevem como iniciar o contêiner usando o
docker run
e, por algum motivo, não escrevem como ele pode ser iniciado através do docker-compose, e é através do docker-compose que ele inicia na maioria das vezes, uma vez que há muito pouca procura. inicie manualmente todos os contêineres, redes, portas abertas e muito mais. O Docker-compose em nome do usuário se parece com um arquivo yaml com configurações. Inclui uma descrição de cada um dos serviços que devem ser iniciados. Minha construção para o ambiente local é a seguinte:
version: '3.1' services: php-fpm: image: otezvikentiy/php7.2-fpm:0.0.11 ports: - '9000:9000' volumes: - ../:/app working_dir: /app container_name: 'php-fpm' nginx: image: nginx:1.15.0 container_name: 'nginx' working_dir: /app ports: - '7777:80' volumes: - ../:/app - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf postgres: image: postgres:9.6 ports: - '5432:5432' container_name: 'postgresql' working_dir: /app restart: always environment: POSTGRES_DB: 'db_name' POSTGRES_USER: 'db_user' POSTGRES_PASSWORD: 'db_pass' volumes: - ./data/dump:/app/dump - ./data/postgresql:/var/lib/postgresql/data rabbitmq: image: rabbitmq:3.7.5-management working_dir: /app hostname: rabbit-mq container_name: 'rabbit-mq' ports: - '15672:15672' - '5672:5672' environment: RABBITMQ_DEFAULT_USER: user RABBITMQ_DEFAULT_PASS: password RABBITMQ_DEFAULT_VHOST: my_vhost elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:6.3.0 container_name: 'elastic-search' environment: - discovery.type=single-node - "discovery.zen.ping.unicast.hosts=elasticsearch" - bootstrap.memory_lock=true - "ES_JAVA_OPTS=-Xms512m -Xmx512m" ports: - 9200:9200 - 9300:9300 working_dir: /app volumes: - ../:/app - ./data/elasticsearch:/usr/share/elasticsearch/data volumes: elasticsearch: postgresql:
docker-compose.yaml para SF4 é um determinado conjunto de serviços: nginx, php-fpm, postgresql, rabbitmq (se você precisar), elasticsearch (se você precisar). Para o ambiente local, isso é suficiente. Para fazer tudo funcionar - há um conjunto mínimo de configurações, sem as quais nada funcionará. Na maioria das vezes, são imagem, volumes, portas, ambiente, working_dir e container_name. Tudo para iniciar esta ou aquela imagem está descrito em sua documentação em
hub.docker.com . Nem sempre existe uma descrição para o docker-compose, mas isso não significa que ele não funcione. É apenas necessário transferir todos os dados recebidos do comando docker run para o docker-composite e tudo funcionará.
Por exemplo, há uma imagem para o RabbitMQ
aqui . Quando você vê ISTO pela primeira vez, causa sentimentos e emoções confusas, mas nem tudo é tão assustador. As etiquetas são indicadas nesta imagem. Geralmente tags - representam imagens diferentes, versões diferentes do aplicativo com diferentes imagens expansíveis. Por exemplo, a tag 3.7.7-alpine significa que esta imagem é mais fina que, por exemplo, 3.7.7, pois é baseada em Alpine. Bem, e também nas tags, na maioria das vezes, as versões do aplicativo são indicadas. Normalmente escolho a versão mais recente e a versão estável do próprio aplicativo e da imagem alpina.
Depois de estudar e selecionar uma tag - muitas vezes você vê algo desse tipo:
docker run -d --hostname my-rabbit --name some-rabbit -e RABBITMQ_DEFAULT_USER=user -e RABBITMQ_DEFAULT_PASS=password rabbitmq:3-management
E o primeiro pensamento é WTF? Como transferir isso para docker-compondo?
Tudo é bem fácil. De fato, esta linha indica todos os mesmos parâmetros que no arquivo yaml, apenas abreviados. Por exemplo, -e é um ambiente no qual vários parâmetros são passados; também pode haver entradas como -p - essas são portas que são chamadas de portas no yaml. Portanto, para usar uma imagem desconhecida de uma maneira de qualidade, você só precisa “google” a janela de encaixe executar abreviações e aplicar os nomes completos no arquivo yaml.
Agora, de volta ao docker-compose.yml, que citei como exemplo acima.
Este exemplo usa minha imagem php7.2 criada como uma extensão da imagem oficial php7.2-fpm-alpine, mas se você não precisar de tantas bibliotecas adicionais, poderá criar sua extensão para a imagem oficial e usá-la. O restante das imagens para LAN são completamente originais e oficiais.
imagem - indica qual imagem baixar. Por exemplo (rabbitmq: 3.7.7-management-alpine).
portas - especifique as portas que o contêiner usará (consulte a documentação da imagem). A porta nginx de exemplo é 80 por padrão. Portanto, se você deseja usar a porta 80, especifique 80:80 aqui e seu site estará disponível no host local. Ou você pode especificar 7777: 80 e, em seguida, seu site estará no URL localhost: 7777. Isso é necessário para que vários projetos possam ser implantados no mesmo host.
volumes - diretórios compartilhados são indicados aqui. Por exemplo, seu projeto está no diretório ~ / projects / my-sf4-app, e o contêiner php está configurado para funcionar com o diretório / app (o mesmo que em / var / www / my-sf4-app). Por conseguinte, seria conveniente para o contêiner ter acesso ao projeto. Assim, em volumes, escrevemos
~/projects/my-sf4-app:/app
(veja este exemplo em docker-compose.yml acima (eu o indiquei de maneira relativa ../:/app)).
Assim, a pasta será compartilhada para o contêiner e poderá executar várias ações, como a
php bin/console doctrine:migrations:migrate
. Também é conveniente usar esses diretórios para salvar os dados do aplicativo. Por exemplo, postgresql, você pode especificar um diretório para armazenar dados do banco de dados e, ao recriar o contêiner, não será necessário rolar um dump ou acessórios.
working_dir - indica o diretório de trabalho do contêiner. Nesse caso, / app (ou por analogia com o vagrant / var / www / my-sf4-app).
ambiente - todas as variáveis para o contêiner são passadas aqui. Por exemplo, para rabbitmq, o nome de usuário e a senha são transmitidos, para o postgresql, o nome da base, o nome de usuário e a senha são passados.
container_name é um campo opcional, mas prefiro especificar, para a conveniência de conectar-se a contêineres. Se não especificado, os nomes padrão com hashes serão atribuídos.
Estes são os principais parâmetros que devem ser especificados. O restante pode ser opcional para configurações adicionais ou de acordo com a documentação do contêiner.
Agora, para iniciar tudo isso, você precisa executar o
docker-compose up -d
no diretório em que o arquivo docker-compose está localizado.
Como e onde armazenar tudo isso para a LAN?
Para a LAN, uso a pasta docker na raiz do projeto.
Ele contém a pasta de dados na qual eu armazeno todas as informações postgresql e elasticsearch, para que, quando você recrie o projeto, não precise rolar os equipamentos do zero. Há também um pai nginx no qual armazeno a configuração para o contêiner nginx local. Sincronizo essas pastas no docker-compose.yml com os arquivos e pastas correspondentes nos contêineres. Também na minha opinião, é muito conveniente escrever scripts bash para trabalhar com o docker. Por exemplo, o script start.sh inicia contêineres, instala o compositor, limpa o cache e migra. Também é conveniente para os colegas do projeto, eles não precisam fazer nada, apenas executam o script e tudo funciona.
Exemplo de script Start.sh
Exemplo de script
Php-fpm-command.sh
Exemplo de script
Connect-to-php-fpm.sh #!/usr/bin/env bash docker exec -i -t --privileged php-fpm bash
O ambiente de desenvolvimento local termina aqui. Parabéns, você pode compartilhar o resultado final com seus colegas! )
Produtivo
Preparação
Suponha que você já tenha escrito algo em uma LAN e queira colocá-lo em um servidor de produção ou em um servidor de teste. Você tem hospedagem na virtualização KVM ou no seu servidor na próxima sala com ar condicionado.
Para implantar um produto ou beta - o servidor deve ter um sistema operacional (idealmente linux) e docker instalado. O Docker pode ser instalado da mesma maneira que na LAN, não há diferenças.
O Docker em produtividade é um pouco diferente da LAN. Em primeiro lugar, você não pode simplesmente pegar e especificar senhas e outras informações e compor o docker. Em segundo lugar, você não pode usar o docker-compose diretamente.
O Docker usa enxame de encaixe e pilha de encaixe para produtividade. Se estiver correto, esse sistema difere apenas em outros comandos e nesse enxame de encaixe é um balanceador de carga para o cluster (novamente um pouco abstrato, mas será mais fácil de entender).
PS: Eu recomendo que você pratique a configuração do docker swarm no Vagrant (por mais paradoxal que isso possa parecer). Uma receita simples para treinamento - escolha um Vagrant vazio com o mesmo sistema operacional do produto e configure-o para iniciar.
Para configurar o docker swarm, basta executar alguns comandos:
docker swarm init --advertise-addr 192.168.***.** (ip- ) mkdir /app ( app) chown docker /app ( ) docker stack deploy -c docker-compose.yml my-first-sf4-docker-app
Agora, consideramos tudo isso com mais detalhes.
docker swarm init --advertise-addr - inicia o docker swarm diretamente e atrapalha um link para que você possa conectar outro servidor a esse "swarm" para que eles funcionem no cluster.
mkdir / app && chown .. - você deve criar todos os diretórios necessários para que a janela de encaixe trabalhe com antecedência, para que durante a compilação não se queixe da falta de diretórios.
pilha do docker deploy -c docker-compose.yml my-first-sf4-docker-app - este comando inicia a montagem do seu próprio aplicativo, um análogo do docker-compose up -d apenas para o enxame do docker.
Para iniciar qualquer montagem, você precisa do mesmo docker-compose.yaml, mas ele já foi ligeiramente modificado especificamente para produtivo / beta.
version: '3.1' services: php-fpm: image: otezvikentiy/php7.2-fpm:0.0.11 ports: - '9000:9000' networks: - my-test-network depends_on: - postgres - rabbitmq volumes: - /app:/app working_dir: /app deploy: replicas: 1 restart_policy: condition: on-failure placement: constraints: [node.role == manager] nginx: image: nginx:1.15.0 networks: - my-test-network working_dir: /app ports: - '80:80' depends_on: - php-fpm volumes: - /app:/app - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf deploy: replicas: 1 restart_policy: condition: on-failure placement: constraints: [node.role == manager] postgres: image: postgres:9.6 ports: - '5432:5432' working_dir: /app networks: - my-test-network secrets: - postgres_db - postgres_user - postgres_pass environment: POSTGRES_DB_FILE: /run/secrets/postgres_db POSTGRES_USER_FILE: /run/secrets/postgres_user POSTGRES_PASSWORD_FILE: /run/secrets/postgres_pass volumes: - ./data/dump:/app/dump - ./data/postgresql:/var/lib/postgresql/data deploy: replicas: 1 restart_policy: condition: on-failure placement: constraints: [node.role == manager] rabbitmq: image: rabbitmq:3.7.5-management networks: - my-test-network working_dir: /app hostname: my-test-sf4-app-rabbit-mq volumes: - /app:/app ports: - '5672:5672' - '15672:15672' secrets: - rabbitmq_default_user - rabbitmq_default_pass - rabbitmq_default_vhost environment: RABBITMQ_DEFAULT_USER_FILE: /run/secrets/rabbitmq_default_user RABBITMQ_DEFAULT_PASS_FILE: /run/secrets/rabbitmq_default_pass RABBITMQ_DEFAULT_VHOST_FILE: /run/secrets/rabbitmq_default_vhost deploy: replicas: 1 restart_policy: condition: on-failure placement: constraints: [node.role == manager] elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:6.3.0 networks: - my-test-network depends_on: - postgres environment: - discovery.type=single-node - discovery.zen.ping.unicast.hosts=elasticsearch - bootstrap.memory_lock=true - ES_JAVA_OPTS=-Xms512m -Xmx512m ports: - 9200:9200 - 9300:9300 working_dir: /app volumes: - /app:/app - ./data/elasticsearch:/usr/share/elasticsearch/data deploy: replicas: 1 restart_policy: condition: on-failure placement: constraints: [node.role == manager] jenkins: image: otezvikentiy/jenkins:0.0.2 networks: - my-test-network ports: - '8080:8080' - '50000:50000' volumes: - /app:/app - ./data/jenkins:/var/jenkins_home - /var/run/docker.sock:/var/run/docker.sock - /usr/bin/docker:/usr/bin/docker deploy: replicas: 1 restart_policy: condition: on-failure placement: constraints: [node.role == manager] volumes: elasticsearch: postgresql: jenkins: networks: my-test-network: secrets: rabbitmq_default_user: file: ./secrets/rabbitmq_default_user rabbitmq_default_pass: file: ./secrets/rabbitmq_default_pass rabbitmq_default_vhost: file: ./secrets/rabbitmq_default_vhost postgres_db: file: ./secrets/postgres_db postgres_user: file: ./secrets/postgres_user postgres_pass: file: ./secrets/postgres_pass
Como você pode ver, o arquivo de configurações do produto é um pouco diferente do arquivo da LAN. Ele adicionou segredos, implantações e redes.
segredos - arquivos para armazenar chaves. As chaves são criadas de maneira bastante simples. Você cria um arquivo com o nome da chave - escreva o valor dentro. Depois disso, em docker-compose.yml, você especifica a seção segredos e transfere toda a lista de arquivos com chaves.
Mais detalhes .
redes - isso cria uma certa grade interna através da qual os contêineres se comunicam. Na LAN - isso é feito automaticamente, mas no produtivo - isso precisa ser feito manualmente. Além disso, você pode especificar configurações adicionais, exceto as padrão.
Mais detalhes .
deploy é a principal diferença entre LAN e Product / Beta.
deploy: replicas: 1 restart_policy: condition: on-failure placement: constraints: [node.role == manager]
Conjunto mínimo de lutadores:
réplicas - indica o número de réplicas que você precisa executar (na verdade, isso é usado se você tiver um cluster e usar o balanceador de carga da janela de encaixe). Por exemplo, você tem dois servidores e os conectou através do enxame de encaixe. Especificando o número 2 aqui, por exemplo, 1 instância será criada em 1 servidor e a segunda no segundo servidor. Assim, a carga no servidor será dividida pela metade.
restart_policy - a política de "aumentar novamente" automaticamente o contêiner, caso ele caia por algum motivo.
canal - o local da instância do contêiner. Por exemplo, há momentos em que você deseja que todas as instâncias de um contêiner girem em apenas 1 de 5 servidores e não sejam distribuídas entre eles.
Eu quero ler a documentação!Portanto, aprimoramos um pouco o que distingue o docker-compose.yaml para LAN da versão do produto / beta. Agora vamos tentar administrar esse negócio.
Suponha que você esteja treinando no Vagrant e na raiz do servidor você já tenha o arquivo configurado para o produto docker-compose.yml sudo apt-get update sudo apt-get -y upgrade sudo apt-get install -y language-pack-en-base export LC_ALL=en_US.UTF-8 export LANGUAGE=en_US.UTF-8 export LANG=en_US.UTF-8 curl -sSl https://get.docker.com/ | sh sudo usermod -aG docker ubuntu sudo apt-get install git sudo docker swarm init --advertise-addr 192.168.128.77 sudo mkdir /app sudo chmod 777 /app -R docker stack deploy -c /docker-compose.yml my-app git clone git@bitbucket.org:JohnDoe/my-app.git /app docker stack ps my-app docker stack ls docker stack services my-app
PS: não opte pelo sudo e pelo 777, é claro que não vale a pena fazê-lo no produtivo. Isso é apenas para aprender velocidade.Portanto, estamos mais interessados nas linhas associadas à janela de encaixe.Primeiro, inicializamos o "enxame" (docker swarm).Em seguida, criamos os diretórios necessários para o trabalho.Faça o download do nabo com o nosso código SF4 no diretório / app.Depois disso, existem três comandos: ps, ls e serviços.Cada um deles é útil à sua maneira. Uso ps com mais frequência, pois exibe o status dos contêineres e parte do erro, se houver.Digamos que os contêineres tenham aumentado, mas alguns deles travam constantemente com um erro e, na pilha docker ps my-app, você vê várias reinicializações. Para ver o motivo da queda, você precisa executar o container docker ps -a - e um container aparecerá que cai constantemente. Haverá muitas instâncias do mesmo contêiner, por exemplo my-app_php-fpm.1. * Algum hash feroz *.Portanto, agora, sabendo o nome do contêiner, execute os logs do docker my-app_php-fpm.1. * Algum hash feroz * e examine os logs. Corrija o erro e reinicie TUDO. Para bater todos os contêineres, você pode fazer isso: docker stack rm my-app
Depois disso, você terá um enxame limpo sem recipientes. Corrija o erro - e novamente a pilha do docker implemente -c docker-compose.yml my-app.