W tym artykule z pomocą , , i zorganizujemy bezproblemowe wdrożenie aplikacji webowej. to technika, która umożliwia natychmiastową aktualizację aplikacji bez odrzucania żadnego żądania. Jest to jedna ze strategii wdrożenia bez przestojów i najlepiej sprawdza się w przypadku aplikacji z jedną instancją, ale z możliwością uruchomienia obok drugiej, gotowej do pracy instancji.
Załóżmy, że masz aplikację webową, z której aktywnie korzysta wielu klientów, i nie można jej w żaden sposób wyłączyć na kilka sekund. A bardzo potrzebujesz wprowadzić aktualizację biblioteki, naprawić błąd lub dodać nową, fajną funkcję. W normalnej sytuacji wymagałoby to zatrzymania aplikacji, wymiany i ponownego uruchomienia. W przypadku Dockera można najpierw zastąpić, a potem ponownie uruchomić, ale wciąż będzie okres, w którym żądania do aplikacji nie będą obsługiwane, ponieważ zwykle aplikacja potrzebuje trochę czasu na początkowe załadowanie. A co jeśli uruchomi się, ale okaże niesprawna? Rozwiążmy tę kwestię przy minimalnych środkach i w maksymalnie elegancki sposób.
DISCLAIMER: Większość artykułu przedstawiona jest w eksperymentalnym formacie — w postaci nagrania sesji konsolowej. Mam nadzieję, że nie będzie to zbyt trudne do zrozumienia, a ten kod wystarczająco dokumentuje siebie sam. Dla atmosfery, wyobraź sobie, że to nie tylko fragmenty kodu, ale papier z „żelaznego” teletajpu.
Interesujące techniki, które trudno znaleźć po prostu czytając kod, są opisane na początku każdego rozdziału. Jeśli coś będzie niejasne — googluj i sprawdzaj w (na szczęście znowu działa z powodu odblokowania Telegrama). Co nie jest w Google — pytaj w komentarzach. Chętnie uzupełnię odpowiedni rozdział „Interesujące techniki”.
Zaczynamy.
$ mkdir blue-green-deployment && cd $_Serwis
Utworzymy próbny serwis i umieścimy go w kontenerze.
Interesujące techniki
cat < file-name( + ) — sposób na stworzenie wielowierszowego pliku jednym poleceniem. Wszystko, co bash odczyta z/dev/stdinpo tej linii i do liniiEOFzostanie zapisane wfile-name.wget -qO- URL() — wyświetli otrzymany dokument HTTP w/dev/stdout(odpowiednikcurl URL).
Drukowanie
Celowo przerywam snippety, aby włączyć podświetlenie dla Pythona. Na końcu będzie jeszcze jeden taki fragment. Uważaj, że w tych miejscach papier został pocięty do przekazania do działu podświetlania (gdzie kod był ręcznie kolorowany markerami), a potem te kawałki wklejono z powrotem.
$ cat < uptimer.pyz http.server import BaseHTTPRequestHandler, HTTPServer
z 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} działa od {t:3.1f} sekund.</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} (ładowanie w {loading_seconds} sek.) uruchomione.')
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 .
Wysyłanie kontekstu budowania do demona Docker 39.42kB
Krok 1/4 : FROM python:alpine
---> 8ecf5a48c789
Krok 2/4 : EXPOSE 8080
---> Używając pamięci podręcznej
---> cf92d174c9d3
Krok 3/4 : COPY uptimer.py app.py
---> a7fbb33d6b7e
Krok 4/4 : CMD [ "python", "-u", ".\/app.py" ]
---> Uruchamianie w 1906b4bd9fdf
Usuwanie kontenera pośredniego 1906b4bd9fdf
---> c1655b996fe8
Pomyślnie zbudowano c1655b996fe8
Pomyślnie otagowano uptimer:latest
$ docker run --rm --detach --name uptimer --publish 8080:8080 uptimer
8f88c944b8bf78974a5727070a94c76aa0b9bb2b3ecf6324b784e782614b2fbf
$ docker ps
ID KONTENERA OBRAZ KOMENDA UTWORZONE STATUS PORTY NAZWY
8f88c944b8bf uptimer "python -u .\/app.py" 3 sekundy temu Uruchomione 5 sekundy 0.0.0.0:8080->8080\/tcp uptimer
$ docker logs uptimer
Uptimer v1.0 (ładowany w 10 sek.) uruchomiony.
$ wget -qSO- http:\/\/localhost:8080
HTTP\/1.0 503 Usługa niedostępna
Serwer: BaseHTTP\/0.6 Python\/3.8.3
Data: Sob, 22 Sie 2020 19:52:40 GMT
Połączenie: zamknięte
Typ treści: text\/html;charset=utf-8
Długość treści: 484
$ wget -qSO- http:\/\/localhost:8080
HTTP\/1.0 200 OK
Serwer: BaseHTTP\/0.6 Python\/3.8.3
Data: Sob, 22 Sie 2020 19:52:45 GMT
Typ treści: text\/html
<h2>Uptimer v1.0 działa od 15,4 sekundy.</h2>
$ docker rm --force uptimer
uptimerSerwer proxy zwrotny
Aby nasza aplikacja mogła niezauważalnie się zmieniać, musi być przed nią jeszcze jakaś instancja, która ukryje tę zmianę. Może to być serwer WWW do . Serwer proxy zwrotny jest ustawiony pomiędzy klientem a aplikacją. Przyjmuje żądania od klientów i przekazuje je do aplikacji, a odpowiedzi aplikacji kieruje do klientów.
Aplikację i serwer proxy zwrotny można połączyć wewnątrz Dockera za pomocą . Dzięki temu kontener z aplikacją nie musi mieć przekierowanego portu w systemie gospodarza, co pozwala maksymalnie izolować aplikację od zagrożeń z zewnątrz.
Jeśli serwer proxy zwrotny będzie działał na innym hoście, trzeba będzie zrezygnować z docker network i połączyć aplikację z serwerem proxy zwrotnym przez sieć hosta, przekierowując port aplikacji parametr --publish, tak jak przy pierwszym uruchomieniu oraz jak w przypadku serwera proxy zwrotnego.
Serwer proxy zwrotny uruchomimy na porcie 80, ponieważ to właśnie ta instancja powinna słuchać zewnątrz. Jeśli port 80 jest zajęty na twoim teście, zmień parametr --publish 80:80 na --publish ANY_FREE_PORT:80.
Interesujące techniki
- «W sieciach dockerowych tworzonych przez użytkownika, kontenery można łączyć nie tylko po adresie IP. Nazwa kontenera jest również rozwiązywana w jego adres IP» (, punkt 5 kodeksu dockera).
Drukowanie
$ 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 działa od 11,5 sekundy.</h2>
$ docker run --detach --publish 80:80 --network web-gateway --name reverse-proxy nginx:alpine
80695a822c19051260c66bf60605dcb4ea66802c754037704968bc42527bf120
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
80695a822c19 nginx:alpine "docker-entrypoint.…" 27 sekund temu Działa 25 sekund 0.0.0.0:80->80/tcp reverse-proxy
a1105f1b583d uptimer "python -u ./app.py" Około minutę temu Działa Około minutę 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: signal process started
$ wget -qSO- http://localhost
HTTP/1.1 200 OK
Server: nginx/1.19.0
Date: Sat, 22 Aug 2020 19:56:24 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
<h2>Uptimer v1.0 działa od 104,1 sekundy.</h2>Bezproblemowe wdrożenie
Wydamy nową wersję aplikacji (z dwukrotnym wzrostem wydajności uruchamiania) i spróbujemy wdrożyć ją bezproblemowo.
Interesujące techniki
echo 'my text' | docker exec -i my-container sh -c 'cat > /my-file.txt'— Zapisz tekstmy textdo pliku/my-file.txtwewnątrz konteneramy-container.cat > /my-file.txt— Zapisz zawartość standardowego wejścia do pliku/dev/stdin.
Drukowanie
$ sed -i "s/app_version = 1/app_version = 2/" uptimer.py
$ docker build --tag uptimer .
Wysyłanie kontekstu budowy do demona Docker 39.94kB
Krok 1/4 : FROM python:alpine
---> 8ecf5a48c789
Krok 2/4 : EXPOSE 8080
---> Wykorzystując pamięć podręczną
---> cf92d174c9d3
Krok 3/4 : COPY uptimer.py app.py
---> 3eca6a51cb2d
Krok 4/4 : CMD [ "python", "-u", ".\/app.py" ]
---> Uruchamianie w 8f13c6d3d9e7
Usuwanie pośredniego kontenera 8f13c6d3d9e7
---> 1d56897841ec
Pomyślnie zbudowano 1d56897841ec
Pomyślnie oznaczono uptimer:latest
$ docker run --detach --rm --name uptimer_BLUE --network web-gateway uptimer
96932d4ca97a25b1b42d1b5f0ede993b43f95fac3c064262c5c527e16c119e02
$ docker logs uptimer_BLUE
Uptimer v2.0 (ładowany w 5 sek.) rozpoczęty.
$ docker run --rm --network web-gateway alpine wget -qO- http://uptimer_BLUE:8080
<h2>Uptimer v2.0 działa od 23,9 sekundy.</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
serwer {
listen 80;
lokalizacja / {
proxy_pass http://uptimer_BLUE:8080;
}
}
$ docker exec reverse-proxy nginx -s reload
2020/06/25 21:22:23 [informacja] 68#68: sygnał proces uruchomiony
$ wget -qO- http://localhost
<h2>Uptimer v2.0 działa od 63,4 sekundy.</h2>
$ docker rm -f uptimer
uptimer
$ wget -qO- http://localhost
<h2>Uptimer v2.0 działa od 84,8 sekundy.</h2>
$ docker ps
ID KONTENERA OBRAZ KOMENDA UTWORZONO STATUS PORTY NAZWY
96932d4ca97a uptimer "python -u .\/app.py" Około minutę temu Uruchomiono Około minutę 8080/tcp uptimer_BLUE
80695a822c19 nginx:alpine "\/docker-entrypoint.…" 8 minut temu Uruchomiono 8 minut 0.0.0.0:80->80/tcp reverse-proxyNa tym etapie obraz jest budowany bezpośrednio na serwerze, co wymaga posiadania tam źródeł aplikacji oraz obciąża serwer zbędną pracą. Następnym krokiem będzie wydzielenie procesu budowy obrazu na oddzielną maszynę (np. w systemie CI) z późniejszym przesyłaniem go na serwer.
Przesyłanie obrazów
Niestety, przesyłanie obrazów z localhost na localhost nie ma sensu, więc tę sekcję można przetestować tylko mając pod ręką dwa hosty z Dockerem. W minimalnym wydaniu wygląda to mniej więcej tak:
$ ssh production-server docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
$ docker image save uptimer | ssh production-server 'docker image load'
Załadowano obraz: uptimer:latest
$ ssh production-server docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
uptimer latest 1d56897841ec 5 minut temu 78.9MBZespół docker save zapisuje dane obrazu w archiwum .tar, co oznacza, że waży około 1.5 razy więcej, niż mogłoby ważyć w formie skompresowanej. Więc spróbujmy go skompresować dla oszczędności czasu i transferu:
$ docker image save uptimer | gzip | ssh production-server 'zcat | docker image load'
Załadowano obraz: uptimer:latestA także, można obserwować proces przesyłania (prawda, potrzebne do tego jest zewnętrzne narzędzie):
$ docker image save uptimer | gzip | pv | ssh production-server 'zcat | docker image load'
25,7MiB 0:01:01 [ 425KiB/s] [ ]
Załadowano obraz: uptimer:latestPorada: Jeśli potrzebujesz wielu parametrów do połączenia z serwerem przez SSH, być może nie używasz pliku
~/.ssh/config.
Przesyłanie obrazu przez docker image save/load — to najbardziej minimalistyczna metoda, ale nie jedyna. Są też inne:
- Container Registry (standard branżowy).
- Połączenie się z docker daemon serwera z innego hosta:
- Zmienna środowiskowa
DOCKER_HOST. - Parametr wiersza poleceń
-Hlub--hostnarzędziadocker-compose. docker context
- Zmienna środowiskowa
Drugi sposób (z trzema wariantami jego realizacji) jest dobrze opisany w artykule .
deploy.sh
Teraz zbierzmy wszystko, co robiliśmy ręcznie w jeden skrypt. Zacznijmy od funkcji najwyższego poziomu, a potem przyjrzymy się pozostałym, używanym w nim.
Interesujące techniki
${parameter?err_msg}— jedno z zaklęć bash-magii (znane jako ). Jeśliparametrnie jest ustawiony, wypiszerr_msgi wyjdź z kodem 1.docker --log-driver journald— domyślnie, drivery logowania Dockera to plik tekstowy bez żadnej rotacji. Przy takim podejściu logi szybko zapychają cały dysk, więc w środowisku produkcyjnym należy zmienić driver na mądrzejszy.
Skrypt wdrożeniowy
deploy() {
local usage_msg="Użycie: ${FUNCNAME[0]} nazwa_obrazu"
local image_name=${1?$usage_msg}
ensure-reverse-proxy || return 2
if get-active-slot $image_name
then
local OLD=${image_name}_BLUE
local new_slot=GREEN
else
local OLD=${image_name}_GREEN
local new_slot=BLUE
fi
local NEW=${image_name}_${new_slot}
echo "Wdrażanie '$NEW' w miejsce '$OLD'..."
docker run
--detach
--restart always
--log-driver journald
--name $NEW
--network web-gateway
$image_name || return 3
echo "Kontener uruchomiony. Sprawdzanie stanu..."
for i in {1..20}
do
sleep 1
if get-service-status $image_name $new_slot
then
echo "Nowa usługa '$NEW' wydaje się być w porządku. Przechodzimy do zmiany nagłówków..."
sleep 2 # Upewnij się, że usługa jest gotowa
set-active-slot $image_name $new_slot || return 4
echo "Usługa '$NEW' działa!"
sleep 2 # Upewnij się, że wszystkie żądania zostały przetworzone
echo "Zamykam '$OLD'..."
docker rm -f $OLD
docker image prune -f
echo "Wdrażanie zakończone pomyślnie!"
return 0
fi
echo "Nowa usługa '$NEW' nie jest jeszcze gotowa. Czekam ($i)..."
done
echo "Nowa usługa '$NEW' nie uruchomiła się, zamykam ją. Wdrażanie nie powiodło się T_T"
docker rm -f $NEW
return 5
}Wykorzystane funkcje:
ensure-reverse-proxy— Upewnia się, że reverse proxy działa (przydatne przy pierwszym wdrożeniu)get-active-slot nazwa_usługi— Określa, który slot jest aktualnie aktywny dla danej usługi (BLUElubGREEN)get-service-status nazwa_usługi slot_wdrożeniowy— Określa, czy usługa jest gotowa do przetwarzania przychodzących żądańset-active-slot nazwa_usługi slot_wdrożeniowy— Zmienia konfigurację nginx w kontenerze reverse proxy
Po kolei:
ensure-reverse-proxy() {
is-container-up reverse-proxy && return 0
echo "Wdrażanie 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?"Użycie: ${FUNCNAME[0]} container_name"}
[ -n "$(docker ps -f name=${container} -q)" ]
return $?
}
get-active-slot() {
local service=${1?"Użycie: ${FUNCNAME[0]} service_name"}
if is-container-up ${service}_BLUE && is-container-up ${service}_GREEN; then
echo "Wykryto kolizję! Zatrzymywanie ${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="Użycie: ${FUNCNAME[0]} service_name deployment_slot"
local service=${1?usage_msg}
local slot=${2?$usage_msg}
case $service in
# Dodaj specyficzne ścieżki healthcheck dla swoich usług tutaj
*) local health_check_port_path=":8080/" ;;
esac
local health_check_address="http://${service}_${slot}${health_check_port_path}"
echo "Wysyłam zapytanie do '$health_check_address' w ramach sieci 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="Użycie: ${FUNCNAME[0]} service_name deployment_slot"
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
}veth_xdp_flush_bq() get-active-slot wymaga drobnych wyjaśnień:
Dlaczego zwraca liczbę, a nie wypisuje ciąg?
I tak w wywołującej funkcji sprawdzamy wynik jej działania, a sprawdzanie kodu powrotu za pomocą bash jest znacznie prostsze niż ciągu. Poza tym, uzyskanie ciągu jest bardzo łatwe:
get-active-slot service && echo BLUE || echo GREEN.
Czy te trzy warunki są wystarczające, aby rozróżnić wszystkie stany?
Dwa wystarczą, ostatnie jest tylko dla pełności, aby nie pisać inaczej.
Nieokreślona pozostaje tylko funkcja zwracająca konfiguracje nginx: get-nginx-config service_name deployment_slot. Na wzór healthcheck, tutaj można zdefiniować dowolną konfigurację dla dowolnej usługi. Z interesujących rzeczy — tylko cat <<- EOF, co pozwala usunąć wszystkie tabulatory na początku. Prawda, że cena dobrego formatowania to mieszane tabulatory z spacjami, co dzisiaj uważane jest za bardzo zły ton. Ale bash wymusza tabulatory, a w konfiguracji nginx też byłoby dobrze mieć normalne formatowanie. Krótko mówiąc, tutaj mieszanka tabulatorów z spacjami wydaje się naprawdę najlepszym rozwiązaniem z najgorszych. Jednakże, w poniższym skrypcie nie zobaczysz tego, ponieważ habr "robi dobrze", zmieniając wszystkie tabulatory na 4 spacje i czyniąc EOF niepoprawnym. .
Żeby nie wstawać dwa razy, od razu opowiem o
cat << 'EOF', który jeszcze pojawi się później. Jeśli napisać po prostucat << EOF, to wewnątrz heredoc następuje interpolacja stringu (ujawniane są zmienne ($foo), wywołania komend ($(bar)) itd.), a jeśli zakończyć znak końca dokumentu w pojedynczych cudzysłowach, to interpolacja jest wyłączona i symbol$wyświetlany jest jak jest. To, co potrzebne do wstawienia skryptu do innego skryptu.
get-nginx-config() {
local usage_msg="Użycie: ${FUNCNAME[0]} nazwa_usługi slot_deploymentu"
local service=${1?$usage_msg}
local slot=${2?$usage_msg}
[ "$slot" == BLUE ] || [ "$slot" == GREEN ] || return 1
local container_name=${service}_${slot}
case $service in
# Dodaj odpowiednie konfiguracje nginx dla swoich usług tutaj
*) nginx-config-simple-service $container_name:8080 ;;
esac
}
nginx-config-simple-service() {
local usage_msg="Użycie: ${FUNCNAME[0]} proxy_pass"
local proxy_pass=${1?$usage_msg}
cat << EOF
server {
listen 80;
location / {
proxy_pass http://$proxy_pass;
}
}
EOF
}To jest cały skrypt. I oto do pobrania przez wget lub curl.
Wykonywanie skryptów parametryzowanych na zdalnym serwerze
Nadszedł czas, aby zapukać do docelowego serwera. Tym razem localhost idealnie pasuje:
$ ssh-copy-id localhost
/usr/bin/ssh-copy-id: INFO: attemptuje zalogować się z nowym kluczem(ami), aby odfiltrować wszystkie, które są już zainstalowane
/usr/bin/ssh-copy-id: INFO: 1 klucz(ów) pozostaje do zainstalowania -- jeśli zostaniesz teraz zapytany, to po to, aby zainstalować nowe klucze
hasło himura@localhost:
Liczba dodanych kluczy: 1
Teraz spróbuj zalogować się do maszyny za pomocą: "ssh 'localhost'"
i upewnij się, że tylko klucz(e), które chciałeś, zostały dodane.Napisaliśmy skrypt wdrożeniowy, który przesyła wstępnie zbudowany obraz na docelowy serwer i bezproblemowo zamienia kontener usługi, ale jak go wykonać na zdalnej maszynie? Skrypt ma argumenty, ponieważ jest uniwersalny i może wdrażać kilka usług pod jednym odwrotnym proxy (konfiguracjami nginx można rozwiązać, jaki URL będzie jaką usługą). Skrypt nie może być przechowywany na serwerze, ponieważ w takim przypadku nie będziemy mogli go automatycznie aktualizować (w celu poprawienia błędów i dodawania nowych usług), a w ogóle, stan = zło.
Rozwiązanie 1: Przechowywać skrypt na serwerze, ale kopiować go za każdym razem przez scp. Następnie połączyć się przez ssh i wykonać skrypt z wymaganymi argumentami.
Wady:
- Dwie czynności zamiast jednej
- Miejsca, do których kopiujesz, może nie być albo nie mieć do niego dostępu, albo skrypt może być wykonywany w momencie zamiany.
- Wskazane jest posprzątać po sobie (usunąć skrypt).
- Już trzy czynności.
Rozwiązanie 2:
- W skrypcie trzymać tylko definicje funkcji i w ogóle nic nie uruchamiać
- Dzięki
seddopisać na końcu wywołanie funkcji - Wysyłać to wszystko bezpośrednio do shh przez pipe (
|)
Zalety:
- Naprawdę bezstanowy
- Bez zbędnych encji
- Czuję się fajnie
Tylko bez Ansible. Tak, wszystko już wymyślono. Tak, rower. Zobacz, jaki prosty, elegancki i minimalistyczny rower:
$ 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
Łączenie z localhost...
Hurra! Funkcja 'deploy' wykonuje się na 'hut' z argumentem 'magic-porridge-pot'Jednak nie możemy być pewni, że na zdalnym hoście jest odpowiedni bash, więc dodamy na początku małą kontrolkę (zamiast ):
if [ "$SHELL" != "/bin/bash" ]
then
echo "Powłoka '$SHELL' nie jest obsługiwana przez 'deploy.sh'. Ustaw powłokę '/bin/bash' dla '$USER@$HOSTNAME'."
exit 1
fiA teraz wszystko naprawdę:
$ 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
Wysyłanie spakowanego obrazu 'uptimer' do 'localhost' za pośrednictwem ssh...
Załadowany obraz: uptimer:latest
Łączenie z 'localhost' za pośrednictwem ssh w celu bezproblemowego wdrożenia 'uptimer'...
Wdrożenie 'uptimer_GREEN' zamiast 'uptimer_BLUE'...
06f5bc70e9c4f930e7b1f826ae2ca2f536023cc01e82c2b97b2c84d68048b18a
Kontener uruchomiony. Sprawdzanie stanu...
Zgłaszanie 'http://uptimer_GREEN:8080/' w ramach sieci docker 'web-gateway':
HTTP/1.0 503 Usługa niedostępna
wget: serwer zwrócił błąd: HTTP/1.0 503 Usługa niedostępna
Nowa usługa 'uptimer_GREEN' jeszcze nie jest gotowa. Oczekiwanie (1)...
Zgłaszanie 'http://uptimer_GREEN:8080/' w ramach sieci docker 'web-gateway':
HTTP/1.0 503 Usługa niedostępna
wget: serwer zwrócił błąd: HTTP/1.0 503 Usługa niedostępna
Nowa usługa 'uptimer_GREEN' jeszcze nie jest gotowa. Oczekiwanie (2)...
Zgłaszanie 'http://uptimer_GREEN:8080/' w ramach sieci docker 'web-gateway':
HTTP/1.0 200 OK
Serwer: BaseHTTP/0.6 Python/3.8.3
Data: Sat, 22 Aug 2020 20:15:50 GMT
Typ treści: text/html
Nowa usługa 'uptimer_GREEN' wydaje się być w porządku. Przełączanie...
nginx: plik konfiguracyjny /etc/nginx/nginx.conf ma poprawną składnię
nginx: test pliku konfiguracyjnego /etc/nginx/nginx.conf zakończony sukcesem
2020/08/22 20:15:54 [ogłoszenie] 97#97: proces sygnałowy uruchomiony
Usługa 'uptimer_GREEN' działa!
Zabijanie 'uptimer_BLUE'...
uptimer_BLUE
Całkowita odzyskana przestrzeń: 0B
Wdrożenie zakończone sukcesem!Można teraz otworzyć w przeglądarce, uruchomić wdrożenie jeszcze raz i upewnić się, że przebiega bezproblemowo poprzez odświeżenie strony na KD podczas wdrożenia.
Nie zapominaj sprzątać po pracy :3
$ docker rm -f uptimer_GREEN reverse-proxy
uptimer_GREEN
reverse-proxy
$ docker network rm web-gateway
web-gateway
$ cd ..
$ rm -r blue-green-deploymentŹródło: habr.com
