Neste artigo, vamos , , e organizar a implantação contínua de uma aplicação web. é uma técnica que permite atualizar uma aplicação instantaneamente, sem rejeitar nenhuma solicitação. É uma das estratégias de zero downtime deployment e é mais adequada para aplicações com uma única instância, mas com a possibilidade de carregar uma segunda instância, pronta para funcionar, ao lado.
Suponha que você tenha uma aplicação web que é utilizada ativamente por muitos clientes, e não pode ficar fora do ar nem por alguns segundos. E você precisa muito implantar uma atualização de biblioteca, corrigir um bug ou adicionar um novo recurso incrível. Em uma situação normal, seria necessário parar a aplicação, substituí-la e reiniciá-la. No caso do Docker, você pode primeiro substituir e, em seguida, reiniciar, mas ainda assim haverá um período em que as solicitações à aplicação não serão processadas, pois geralmente a aplicação leva um tempo para inicializar. E se ela iniciar, mas não funcionar? Vamos resolver essa tarefa com o mínimo de meios e da maneira mais elegante.
AVISO: A maior parte deste artigo é apresentada em um formato experimental — como um registro de sessão no console. Espero que isso não seja muito difícil de entender e que este código se documente suficientemente por si só. Para criar uma atmosfera, imagine que não se trata apenas de fragmentos de código, mas de papel de uma máquina de escrever ‘metálica’.
Técnicas interessantes que são difíceis de encontrar apenas lendo o código estão descritas no início de cada seção. Se algo não estiver claro, pesquise e verifique em (felizmente, ele está funcionando novamente, devido à desbloqueação do Telegram). O que não pode ser encontrado, pergunte nos comentários. Farei questão de complementar a seção ‘Técnicas interessantes’.
Vamos começar.
$ mkdir blue-green-deployment && cd $_Serviço
Vamos criar um serviço experimental e colocá-lo em um contêiner.
Técnicas interessantes
cat < file-name( + ) — uma maneira de criar um arquivo múltiplo com um único comando. Tudo o que o bash ler a partir/dev/stdindesta linha e até a linhaEOFserá gravado emfile-name.wget -qO- URL() — imprimir o documento obtido via HTTP para/dev/stdout(equivalente acurl URL).
Impressão
Eu quebro intencionalmente o snippet para incluir destaque para Python. No final, haverá mais um pedaço assim. Considere que nesses lugares o papel foi cortado para ser enviado ao departamento de destaque (onde o código foi colorido manualmente com marcadores), e depois esses pedaços foram colados de volta.
$ cat < uptimer.pyfrom http.server import BaseHTTPRequestHandler, HTTPServer
from time import monotonic
app_version = 1
app_name = f'Uptimer v{app_version}.0'
loading_seconds = 15 - app_version * 5
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == '':
try:
t = monotonic() - server_start
if t < loading_seconds:
self.send_error(503)
else:
self.send_response(200)
self.send_header('Content-Type', 'text/html')
self.end_headers()
response = f'<h2>{app_name} está em execução por {t:3.1f} segundos.</h2>n'
self.wfile.write(response.encode('utf-8'))
except Exception:
self.send_error(500)
else:
self.send_error(404)
httpd = HTTPServer(('', 8080), Handler)
server_start = monotonic()
print(f'{app_name} (carregando em {loading_seconds} seg.) iniciado.')
httpd.serve_forever()EOF
$ cat << EOF > Dockerfile
FROM python:alpine
EXPOSE 8080
COPY uptimer.py app.py
CMD [ "python", "-u", ".\/app.py" ]
EOF
$ docker build --tag uptimer .
Enviando contexto de construção para o daemon Docker 39.42kB
Etapa 1/4 : FROM python:alpine
---> 8ecf5a48c789
Etapa 2/4 : EXPOSE 8080
---> Usando cache
---> cf92d174c9d3
Etapa 3/4 : COPY uptimer.py app.py
---> a7fbb33d6b7e
Etapa 4/4 : CMD [ "python", "-u", ".\/app.py" ]
---> Executando em 1906b4bd9fdf
Removendo container intermediário 1906b4bd9fdf
---> c1655b996fe8
Construído com sucesso c1655b996fe8
Tagueado com sucesso uptimer:latest
$ docker run --rm --detach --name uptimer --publish 8080:8080 uptimer
8f88c944b8bf78974a5727070a94c76aa0b9bb2b3ecf6324b784e782614b2fbf
$ docker ps
ID DO CONTÊINER IMAGEM COMANDO CRIADO STATUS PORTAS NOMES
8f88c944b8bf uptimer "python -u .\/app.py" há 3 segundos Em funcionamento há 5 segundos 0.0.0.0:8080->8080\/tcp uptimer
$ docker logs uptimer
Uptimer v1.0 (carrega em 10 seg.) iniciado.
$ wget -qSO- http:\/\/localhost:8080
HTTP\/1.0 503 Serviço Indisponível
Servidor: BaseHTTP\/0.6 Python\/3.8.3
Data: Sáb, 22 Ago 2020 19:52:40 GMT
Conexão: fechar
Content-Type: text\/html;charset=utf-8
Content-Length: 484
$ wget -qSO- http:\/\/localhost:8080
HTTP\/1.0 200 OK
Servidor: BaseHTTP\/0.6 Python\/3.8.3
Data: Sáb, 22 Ago 2020 19:52:45 GMT
Content-Type: text\/html
<h2>Uptimer v1.0 está em execução há 15.4 segundos.</h2>
$ docker rm --force uptimer
uptimerProxy reverso
Para que nosso aplicativo tenha a capacidade de mudar discretamente, é necessário que haja alguma entidade na frente dele que esconda sua troca. Isso pode ser um servidor web em . O proxy reverso é instalado entre o cliente e o aplicativo. Ele aceita solicitações dos clientes e as redireciona para o aplicativo, enquanto as respostas do aplicativo são enviadas de volta aos clientes.
O aplicativo e o proxy reverso podem ser conectados dentro do Docker usando . Assim, o contêiner com o aplicativo pode nem precisar de uma porta exposta no sistema host, o que permite isolar o aplicativo ao máximo das ameaças externas.
Se o proxy reverso estiver em um host diferente, será necessário abrir mão da docker network e conectar o aplicativo ao proxy reverso através da rede do host, expondo a porta da aplicação parâmetro --publish, assim como na primeira execução e no proxy reverso.
O proxy reverso será iniciado na porta 80, pois essa é exatamente a entidade que deve escutar o exterior. Se a porta 80 estiver ocupada em seu host de teste, mude o parâmetro --publish 80:80 para --publish ANY_FREE_PORT:80.
Técnicas interessantes
- “Nas redes Docker criadas pelo usuário, é possível se conectar aos contêineres não apenas pelo endereço IP. O nome do contêiner também é resolvido para seu endereço IP” (, item 5 do código Docker).
Impressão
$ docker network create web-gateway
5dba128fb3b255b02ac012ded1906b7b4970b728fb7db3dbbeccc9a77a5dd7bd
$ docker run --detach --rm --name uptimer --network web-gateway uptimer
a1105f1b583dead9415e99864718cc807cc1db1c763870f40ea38bc026e2d67f
$ docker run --rm --network web-gateway alpine wget -qO- http://uptimer:8080
<h2>Uptimer v1.0 está em execução há 11,5 segundos.</h2>
$ docker run --detach --publish 80:80 --network web-gateway --name reverse-proxy nginx:alpine
80695a822c19051260c66bf60605dcb4ea66802c754037704968bc42527bf120
$ docker ps
ID DO CONTÊINER IMAGEM COMANDO CRIADO STATUS PORTAS NOME
80695a822c19 nginx:alpine "\/docker-entrypoint.…" há 27 segundos Em execução há 25 segundos 0.0.0.0:80->80/tcp reverse-proxy
a1105f1b583d uptimer "python -u .\/app.py" há cerca de um minuto Em execução há cerca de um minuto 8080/tcp uptimer
$ cat << EOF > uptimer.conf
server {
listen 80;
location / {
proxy_pass http://uptimer:8080;
}
}
EOF
$ docker cp ./uptimer.conf reverse-proxy:/etc/nginx/conf.d/default.conf
$ docker exec reverse-proxy nginx -s reload
2020/06/23 20:51:03 [notice] 31#31: processo de sinal iniciado
$ wget -qSO- http://localhost
HTTP/1.1 200 OK
Servidor: nginx/1.19.0
Data: Sáb, 22 Ago 2020 19:56:24 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
<h2>Uptimer v1.0 está em execução há 104,1 segundos.</h2>Implantação sem interrupções
Vou implantar uma nova versão do aplicativo (com um aumento de desempenho de inicialização de duas vezes) e tentarei implantá-la sem interrupções.
Técnicas interessantes
echo 'my text' | docker exec -i my-container sh -c 'cat > /my-file.txt'— Gravar textomy text. Isso facilitará a configuração e o uso do armazenamento HashiCorp com o GitLab./my-file.txtdentro do contêinermy-container.cat > /my-file.txt— Gravar no arquivo o conteúdo da entrada padrão/dev/stdin.
Impressão
$ sed -i "s/app_version = 1/app_version = 2/" uptimer.py
$ docker build --tag uptimer .
Enviando contexto de construção para o daemon do Docker 39.94kB
Etapa 1/4 : FROM python:alpine
---> 8ecf5a48c789
Etapa 2/4 : EXPOSE 8080
---> Usando cache
---> cf92d174c9d3
Etapa 3/4 : COPY uptimer.py app.py
---> 3eca6a51cb2d
Etapa 4/4 : CMD [ "python", "-u", "./app.py" ]
---> Executando em 8f13c6d3d9e7
Removendo o container intermediário 8f13c6d3d9e7
---> 1d56897841ec
Construído com sucesso 1d56897841ec
Marcado com sucesso uptimer:latest
$ docker run --detach --rm --name uptimer_BLUE --network web-gateway uptimer
96932d4ca97a25b1b42d1b5f0ede993b43f95fac3c064262c5c527e16c119e02
$ docker logs uptimer_BLUE
Uptimer v2.0 (carrega em 5 seg.) iniciado.
$ docker run --rm --network web-gateway alpine wget -qO- http://uptimer_BLUE:8080
<h2>Uptimer v2.0 está rodando há 23.9 segundos.</h2>
$ sed s/uptimer/uptimer_BLUE/ uptimer.conf | docker exec --interactive reverse-proxy sh -c 'cat > /etc/nginx/conf.d/default.conf'
$ docker exec reverse-proxy cat /etc/nginx/conf.d/default.conf
server {
listen 80;
location / {
proxy_pass http://uptimer_BLUE:8080;
}
}
$ docker exec reverse-proxy nginx -s reload
2020/06/25 21:22:23 [notice] 68#68: sinal do processo iniciado
$ wget -qO- http://localhost
<h2>Uptimer v2.0 está rodando há 63.4 segundos.</h2>
$ docker rm -f uptimer
uptimer
$ wget -qO- http://localhost
<h2>Uptimer v2.0 está rodando há 84.8 segundos.</h2>
$ docker ps
ID DO CONTÊINER IMAGEM COMANDO CRIADO STATUS PORTAS NOMES
96932d4ca97a uptimer "python -u ./app.py" Há cerca de um minuto Em funcionamento Há cerca de um minuto 8080/tcp uptimer_BLUE
80695a822c19 nginx:alpine "docker-entrypoint.…" 8 minutos atrás Em funcionamento 8 minutos 0.0.0.0:80->80/tcp reverse-proxyNesta etapa, a imagem é construída diretamente no servidor, o que requer a presença do código-fonte do aplicativo lá, além de sobrecarregar o servidor com trabalho extra. O próximo passo será a construção da imagem em uma máquina separada (por exemplo, em um sistema CI), seguida pela transmissão dela para o servidor.
Transferência de imagens
Infelizmente, não faz sentido transferir imagens de localhost para localhost, então esta seção só pode ser testada tendo em mãos dois hosts com Docker. No básico, isso se apresenta mais ou menos assim:
$ ssh production-server docker image ls
REPOSITÓRIO TAG ID DA IMAGEM CRIADO TAMANHO
$ docker image save uptimer | ssh production-server 'docker image load'
Imagem carregada: uptimer:latest
$ ssh production-server docker image ls
REPOSITÓRIO TAG ID DA IMAGEM CRIADO TAMANHO
uptimer latest 1d56897841ec há 5 minutos 78.9MBTime docker save salva os dados da imagem em um arquivo .tar, ou seja, ele pesa aproximadamente 1,5 vezes mais do que poderia pesar em formato comprimido. Então, vamos compactá-lo em nome da economia de tempo e tráfego:
$ docker image save uptimer | gzip | ssh production-server 'zcat | docker image load'
Imagem carregada: uptimer:latestAlém disso, é possível acompanhar o processo de transferência (embora seja necessária uma ferramenta externa para isso):
$ docker image save uptimer | gzip | pv | ssh production-server 'zcat | docker image load'
25,7MiB 0:01:01 [ 425KiB/s] [ ]
Imagem carregada: uptimer:latestDica: Se você precisar de muitos parâmetros para se conectar ao servidor via SSH, talvez você não esteja utilizando o arquivo
~/.ssh/config.
Transferência da imagem via docker image save/load — é o método mais minimalista, mas não o único. Existem outros:
- Container Registry (padrão da indústria).
- Conectar ao daemon Docker do servidor a partir de outro host:
- Variável de ambiente
DOCKER_HOST. - Parâmetro de linha de comando
-Hou--hostferramentadocker-compose. docker context
- Variável de ambiente
A segunda abordagem (com três opções de implementação) está bem descrita no artigo .
deploy.sh
Agora vamos juntar tudo o que fizemos manualmente em um único script. Começaremos pela função principal e, em seguida, veremos as outras que são usadas nela.
Técnicas interessantes
${parameter?err_msg}— um dos encantamentos da magia bash (aka ). Separameternão estiver definido, exibirerr_msge sair com o código 1.docker --log-driver journald— por padrão, o driver de log do Docker é um arquivo de texto sem qualquer rotação. Com essa abordagem, os logs rapidamente ocupam todo o disco, portanto, para um ambiente de produção, é necessário alterar o driver para um mais inteligente.
Script de implantação
deploy() {
local usage_msg="Uso: ${FUNCNAME[0]} nome_da_imagem"
local image_name=${1?$usage_msg}
ensure-reverse-proxy || return 2
if get-active-slot $image_name
then
local OLD=${image_name}_AZUL
local new_slot=VERDE
else
local OLD=${image_name}_VERDE
local new_slot=AZUL
fi
local NEW=${image_name}_${new_slot}
echo "Implantando '$NEW' no lugar de '$OLD'..."
docker run
--detach
--restart always
--log-driver journald
--name $NEW
--network web-gateway
$image_name || return 3
echo "Container iniciado. Verificando saúde..."
for i in {1..20}
do
sleep 1
if get-service-status $image_name $new_slot
then
echo "Novo serviço '$NEW' parece estar OK. Alternando cabeçotes..."
sleep 2 # Garantir que o serviço esteja pronto
set-active-slot $image_name $new_slot || return 4
echo "Serviço '$NEW' está ativo!"
sleep 2 # Garantir que todas as solicitações foram processadas
echo "Matando '$OLD'..."
docker rm -f $OLD
docker image prune -f
echo "Implantação bem-sucedida!"
return 0
fi
echo "Novo serviço '$NEW' ainda não está pronto. Aguardando ($i)..."
done
echo "Novo serviço '$NEW' não foi iniciado, matando-o. Falha ao implantar T_T"
docker rm -f $NEW
return 5
}Funções utilizadas:
ensure-reverse-proxy— Garante que o reverse proxy está funcionando (útil para a primeira implantação)get-active-slot nome_do_serviço— Determina qual slot está ativo atualmente para o serviço especificado (AZULouVERDE)get-service-status nome_do_serviço slot_de_implantação— Determina se o serviço está pronto para receber solicitaçõesset-active-slot nome_do_serviço slot_de_implantação— Altera a configuração do nginx no contêiner do reverse proxy
Na ordem:
ensure-reverse-proxy() {
is-container-up reverse-proxy && return 0
echo "Implementando reverse-proxy..."
docker network create web-gateway
docker run
--detach
--restart always
--log-driver journald
--name reverse-proxy
--network web-gateway
--publish 80:80
nginx:alpine || return 1
docker exec --interactive reverse-proxy sh -c "> /etc/nginx/conf.d/default.conf"
docker exec reverse-proxy nginx -s reload
}
is-container-up() {
local container=${1?"Uso: ${FUNCNAME[0]} nome_do_container"}
[ -n "$(docker ps -f name=${container} -q)" ]
return $?
}
get-active-slot() {
local service=${1?"Uso: ${FUNCNAME[0]} nome_do_serviço"}
if is-container-up ${service}_BLUE && is-container-up ${service}_GREEN; then
echo "Colisão detectada! Parando ${service}_GREEN..."
docker rm -f ${service}_GREEN
return 0 # BLUE
fi
if is-container-up ${service}_BLUE && ! is-container-up ${service}_GREEN; then
return 0 # BLUE
fi
if ! is-container-up ${service}_BLUE; then
return 1 # GREEN
fi
}
get-service-status() {
local usage_msg="Uso: ${FUNCNAME[0]} nome_do_serviço slot_de_deploy"
local service=${1?usage_msg}
local slot=${2?$usage_msg}
case $service in
# Adicione caminhos específicos de healthcheck para seus serviços aqui
*) local health_check_port_path=":8080/" ;;
esac
local health_check_address="http://${service}_${slot}${health_check_port_path}"
echo "Solicitando '$health_check_address' dentro da rede docker 'web-gateway':"
docker run --rm --network web-gateway alpine
wget --timeout=1 --quiet --server-response $health_check_address
return $?
}
set-active-slot() {
local usage_msg="Uso: ${FUNCNAME[0]} nome_do_serviço slot_de_deploy"
local service=${1?$usage_msg}
local slot=${2?$usage_msg}
[ "$slot" == BLUE ] || [ "$slot" == GREEN ] || return 1
get-nginx-config $service $slot | docker exec --interactive reverse-proxy sh -c "cat > /etc/nginx/conf.d/$service.conf"
docker exec reverse-proxy nginx -t || return 2
docker exec reverse-proxy nginx -s reload
}Função get-active-slot exige algumas explicações:
Por que retorna um número em vez de imprimir uma string?
Ainda assim, na função chamadora estamos checando o resultado do seu trabalho, e verificar o código de saída com bash é muito mais fácil do que uma string. Além disso, obter uma string dela é muito simples:
get-active-slot service && echo BLUE || echo GREEN.
São três condições suficientes para distinguir todos os estados?
Duas são suficientes, a última aqui é apenas para completar, para não ter que escrever else.
Apenas a função que retorna as configurações do nginx permanece indefinida: get-nginx-config nome_do_serviço slot_de_deploy. Por analogia com o healthcheck, aqui é possível definir qualquer configuração para qualquer serviço. O interessante é apenas cat <<- EOF, o que permite remover todas as guias do início. No entanto, o preço de uma formatação adequada é a mistura de guias com espaços, o que hoje é considerado um grande mau jeito. Mas o bash força guias, e no config do nginx também seria bom ter uma formatação adequada. Em resumo, aqui a mistura de guias com espaços parece realmente ser a melhor solução dentre as opções ruins. No entanto, no snippet abaixo você não verá isso, pois o habr "faz bem", trocando todas as guias por 4 espaços e tornando o EOF inválido. .
Para não fazer isso em vão, vou falar sobre
cat << 'EOF', que ainda aparecerá mais adiante. Se escrevesse apenascat << EOF, dentro do heredoc ocorre a interpolação da string (variáveis são expandidas ($foo), chamadas de comandos ($(bar)) etc.), mas se você colocar o sinal de fim do documento entre aspas simples, a interpolação é desativada e o símbolo$é exibido tal como é. Isso é necessário para inserir um script dentro de outro script.
get-nginx-config() {
local usage_msg="Uso: ${FUNCNAME[0]} nome_do_serviço slot_de_implantaçã"
local service=${1?$usage_msg}
local slot=${2?$usage_msg}
[ "$slot" == BLUE ] || [ "$slot" == GREEN ] || return 1
local container_name=${service}_${slot}
case $service in
# Adicione configurações específicas do nginx para seus serviços aqui
*) nginx-config-simple-service $container_name:8080 ;;
esac
}
nginx-config-simple-service() {
local usage_msg="Uso: ${FUNCNAME[0]} proxy_pass"
local proxy_pass=${1?$usage_msg}
cat << EOF
server {
listen 80;
location / {
proxy_pass http://$proxy_pass;
}
}
EOF
}Esse é o script completo. E aqui está para download via wget ou curl.
Execução de scripts parametrizados em um servidor remoto
É hora de se conectar ao servidor de destino. Desta vez localhost funcionará bem:
$ ssh-copy-id localhost
/usr/bin/ssh-copy-id: INFO: tentando logar com a(s) nova(s) chave(s), para filtrar qualquer uma que já esteja instalada
/usr/bin/ssh-copy-id: INFO: 1 chave(s) permanece(m) para ser(em) instalada(s) -- se você for solicitado agora, é para instalar as novas chaves
senha de himura@localhost:
Número de chave(s) adicionada(s): 1
Agora tente fazer login na máquina, com: "ssh 'localhost'"
e verifique se apenas a(s) chave(s) que você queria foram adicionadas.Criamos um script de implantação que transfere uma imagem previamente compilada para o servidor de destino e substitui o container do serviço sem interrupções, mas como executá-lo em uma máquina remota? O script tem argumentos, pois é versátil e pode implantar vários serviços sob um único reverse proxy (as configurações do nginx podem resolver qual serviço aparecerá em qual URL). O script não pode ser armazenado no servidor, pois assim não poderemos atualizá-lo automaticamente (para correção de bugs e adição de novos serviços), e, de um modo geral, estado = mal.
Solução 1: Armazenar o script no servidor, mas copiá-lo toda vez através de scp. Em seguida, conectar-se via ssh e executar o script com os argumentos necessários.
Pontos negativos:
- Duas ações em vez de uma
- O local para onde você copia pode não existir, ou você pode não ter acesso, ou o script pode ser executado no momento em que a substituição ocorre.
- É desejável limpar o que sobrou (excluir o script).
- Já são três ações.
Solução 2:
- O script deve conter apenas definições de funções e não executar nada
- Com
sedadicionar uma chamada de função no final - Enviar tudo isso diretamente para o shh através de um pipe (
|)
Acesso direto aos sistemas de cabos transpacíficos. Ping mínimo para a China, Japão, Austrália e costa oeste dos EUA.
- Verdadeiramente sem estado
- Sem entidades boilerplate
- Sentindo-se bem
Vamos deixar isso de lado e evitar o Ansible. Sim, tudo já foi inventado. Sim, bicicleta. Veja só, que bicicleta simples, elegante e minimalista:
$ cat < deploy.sh#!/bin/bash
usage_msg="Usage: $0 ssh_address local_image_tag"
ssh_address=${1?$usage_msg}
image_name=${2?$usage_msg}
echo "Connecting to '$ssh_address' via ssh to seamlessly deploy '$image_name'..."
( sed "$a deploy $image_name" | ssh -T $ssh_address ) << 'END_OF_SCRIPT'
deploy() {
echo "Yay! The '${FUNCNAME[0]}' function is executing on '$(hostname)' with argument '$1'"
}
END_OF_SCRIPTEOF
$ chmod +x deploy.sh
$ ./deploy.sh localhost magic-porridge-pot
Conectando ao localhost...
Oba! A função 'deploy' está sendo executada em 'hut' com o argumento 'magic-porridge-pot'No entanto, não podemos ter certeza de que o host remoto tem um bash adequado, então vamos adicionar uma verificação no início (isso é em vez de ):
if [ "$SHELL" != "/bin/bash" ]
then
echo "O shell '$SHELL' não é suportado pelo 'deploy.sh'. Defina um shell '/bin/bash' para '$USER@$HOSTNAME'."
exit 1
fiE agora tudo de verdade:
$ docker exec reverse-proxy rm /etc/nginx/conf.d/default.conf
$ wget -qO deploy.sh https://git.io/JUURc
$ chmod +x deploy.sh
$ ./deploy.sh localhost uptimer
Enviando imagem comprimida 'uptimer' para 'localhost' via ssh...
Imagem carregada: uptimer:latest
Conectando a 'localhost' via ssh para implantar 'uptimer' de forma contínua...
Implantando 'uptimer_GREEN' no lugar de 'uptimer_BLUE'...
06f5bc70e9c4f930e7b1f826ae2ca2f536023cc01e82c2b97b2c84d68048b18a
Container iniciado. Verificando saúde...
Solicitando 'http://uptimer_GREEN:8080/' na rede docker 'web-gateway':
HTTP/1.0 503 Serviço Indisponível
wget: servidor retornou erro: HTTP/1.0 503 Serviço Indisponível
Novo serviço 'uptimer_GREEN' ainda não está pronto. Aguardando (1)...
Solicitando 'http://uptimer_GREEN:8080/' na rede docker 'web-gateway':
HTTP/1.0 503 Serviço Indisponível
wget: servidor retornou erro: HTTP/1.0 503 Serviço Indisponível
Novo serviço 'uptimer_GREEN' ainda não está pronto. Aguardando (2)...
Solicitando 'http://uptimer_GREEN:8080/' na rede docker 'web-gateway':
HTTP/1.0 200 OK
Servidor: BaseHTTP/0.6 Python/3.8.3
Data: Sáb, 22 Ago 2020 20:15:50 GMT
Tipo de Conteúdo: text/html
Novo serviço 'uptimer_GREEN' parece OK. Mudando cabeçotes...
nginx: o arquivo de configuração /etc/nginx/nginx.conf está ok
nginx: teste do arquivo de configuração /etc/nginx/nginx.conf foi bem-sucedido
2020/08/22 20:15:54 [aviso] 97#97: sinal de processo iniciado
O serviço 'uptimer_GREEN' está ativo!
Matando 'uptimer_BLUE'...
uptimer_BLUE
Espaço total recuperado: 0B
Implantação bem-sucedida!Agora você pode abrir no navegador, executar a implantação mais uma vez e garantir que ela ocorra sem interrupções atualizando a página rapidamente durante a execução.
Não se esqueça de limpar após o trabalho :3
$ docker rm -f uptimer_GREEN reverse-proxy
uptimer_GREEN
reverse-proxy
$ docker network rm web-gateway
web-gateway
$ cd ..
$ rm -r blue-green-deploymentFonte: habr.com
