في هذه المقالة نستخدم سحق, سه, عامل ميناء и NGINX نقوم بتنظيم تخطيط سلس لتطبيق الويب. نشر الأزرق والأخضر هي تقنية تسمح لك بتحديث التطبيق على الفور دون رفض طلب واحد. إنها إحدى إستراتيجيات نشر وقت التوقف الصفري وهي الأنسب للتطبيقات ذات مثيل واحد، ولكن مع القدرة على تحميل مثيل ثانٍ جاهز للتشغيل في مكان قريب.
لنفترض أن لديك تطبيق ويب يعمل معه العديد من العملاء بنشاط، ولا توجد طريقة على الإطلاق لإيقافه لبضع ثوانٍ. وتحتاج حقًا إلى طرح تحديث للمكتبة أو إصلاح الأخطاء أو ميزة جديدة رائعة. في الوضع الطبيعي، سوف تحتاج إلى إيقاف التطبيق واستبداله وتشغيله مرة أخرى. في حالة عامل الإرساء، يمكنك الاستبدال أولاً، ثم إعادة التشغيل، ولكن ستظل هناك فترة لا تتم فيها معالجة الطلبات المقدمة إلى التطبيق، لأن التطبيق عادةً ما يستغرق بعض الوقت للتحميل الأولي. ماذا لو بدأ ولكن لا يعمل؟ هذه هي المهمة، دعونا نحلها بأقل الوسائل وبأكبر قدر ممكن من الأناقة.
إخلاء المسؤولية: يتم تقديم معظم المقالة بتنسيق تجريبي - كتسجيل لجلسة وحدة التحكم. نأمل ألا يكون من الصعب جدًا قراءته، وأن يكون هذا الرمز موثقًا ذاتيًا بدرجة كافية. بالنسبة للغلاف الجوي، تخيل أن هذه ليست مجرد مقتطفات من التعليمات البرمجية، ولكنها ورقة من آلة كاتبة "حديدية".
يتم وصف التقنيات المثيرة للاهتمام التي يصعب البحث عنها عبر Google بمجرد قراءة الكود في بداية كل قسم. إذا كان هناك شيء آخر غير واضح - جوجل وتسجيل الوصول شرح (لحسن الحظ، يعمل مرة أخرى، فيما يتعلق بإلغاء حظر البرقية). ما لم يتم البحث عنه في Google - اسأل في التعليقات. بكل سرور سأكمل القسم المقابل "تقنيات مثيرة للاهتمام".
هيا بنا نبدأ.
$ mkdir blue-green-deployment && cd $_
خدمة
لنقم بعمل خدمة تجريبية ونضعها في حاوية.
تقنيات مثيرة للاهتمام
cat << EOF > file-name (هنا وثيقة + إعادة توجيه I / O) هي طريقة لإنشاء ملف متعدد الأسطر باستخدام أمر واحد. أي شيء باش يقرأ منه /dev/stdin بعد هذا السطر وقبل السطر EOF سيتم تسجيلها في file-name.
wget -qO- URL (شرح) - إخراج المستند المستلم عبر HTTP إلى /dev/stdout (التناظرية curl URL).
اطبع
لقد قمت بكسر المقتطف عن قصد لتمكين التمييز في لغة بايثون. سيكون هناك واحد آخر في النهاية. ضع في اعتبارك أنه في هذه الأماكن تم قطع الورق لتمريره إلى قسم التمييز (حيث تم رسم الكود يدويًا باستخدام أدوات التمييز)، ثم تم لصق هذه القطع مرة أخرى.
$ cat << EOF > uptimer.py
from 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} is running for {t:3.1f} seconds.</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} (loads in {loading_seconds} sec.) started.')
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 .
Sending build context to Docker daemon 39.42kB
Step 1/4 : FROM python:alpine
---> 8ecf5a48c789
Step 2/4 : EXPOSE 8080
---> Using cache
---> cf92d174c9d3
Step 3/4 : COPY uptimer.py app.py
---> a7fbb33d6b7e
Step 4/4 : CMD [ "python", "-u", "./app.py" ]
---> Running in 1906b4bd9fdf
Removing intermediate container 1906b4bd9fdf
---> c1655b996fe8
Successfully built c1655b996fe8
Successfully tagged uptimer:latest
$ docker run --rm --detach --name uptimer --publish 8080:8080 uptimer
8f88c944b8bf78974a5727070a94c76aa0b9bb2b3ecf6324b784e782614b2fbf
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8f88c944b8bf uptimer "python -u ./app.py" 3 seconds ago Up 5 seconds 0.0.0.0:8080->8080/tcp uptimer
$ docker logs uptimer
Uptimer v1.0 (loads in 10 sec.) started.
$ wget -qSO- http://localhost:8080
HTTP/1.0 503 Service Unavailable
Server: BaseHTTP/0.6 Python/3.8.3
Date: Sat, 22 Aug 2020 19:52:40 GMT
Connection: close
Content-Type: text/html;charset=utf-8
Content-Length: 484
$ wget -qSO- http://localhost:8080
HTTP/1.0 200 OK
Server: BaseHTTP/0.6 Python/3.8.3
Date: Sat, 22 Aug 2020 19:52:45 GMT
Content-Type: text/html
<h2>Uptimer v1.0 is running for 15.4 seconds.</h2>
$ docker rm --force uptimer
uptimer
الوكيل العكسي
لكي يتمكن تطبيقنا من التغيير بشكل غير محسوس، من الضروري أن يكون هناك كيان آخر أمامه يخفي استبداله. يمكن أن يكون خادم الويب NGINX в وضع الوكيل العكسي. يتم إنشاء وكيل عكسي بين العميل والتطبيق. يتلقى الطلبات من العملاء ويعيد توجيهها إلى التطبيق، ويرسل استجابات التطبيق إلى العملاء.
يمكن ربط التطبيق والوكيل العكسي داخل عامل الإرساء باستخدام شبكة عامل الميناء. وبالتالي، فإن الحاوية التي تحتوي على التطبيق لا تحتاج حتى إلى إعادة توجيه المنفذ في النظام المضيف، وهذا يسمح لك بعزل التطبيق قدر الإمكان عن التهديدات الخارجية.
إذا كان الوكيل العكسي يعيش على مضيف آخر، فسيتعين عليك التخلي عن شبكة الإرساء وتوصيل التطبيق بالوكيل العكسي من خلال الشبكة المضيفة عن طريق إعادة توجيه المنفذ التطبيقات معامل --publishكما في البداية الأولى وكما هو الحال مع الوكيل العكسي.
سنقوم بتشغيل الوكيل العكسي على المنفذ 80، لأن هذا هو بالضبط الكيان الذي يجب أن يستمع إلى الخارج. إذا كان المنفذ 80 مشغولاً على مضيف الاختبار الخاص بك، فقم بتغيير المعلمة --publish 80:80 في --publish ANY_FREE_PORT:80.
$ 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 is running for 11.5 seconds.</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 seconds ago Up 25 seconds 0.0.0.0:80->80/tcp reverse-proxy
a1105f1b583d uptimer "python -u ./app.py" About a minute ago Up About a minute 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 is running for 104.1 seconds.</h2>
نشر سلس
فلنطرح إصدارًا جديدًا من التطبيق (مع تعزيز أداء بدء التشغيل مرتين) ونحاول نشره بسلاسة.
تقنيات مثيرة للاهتمام
echo 'my text' | docker exec -i my-container sh -c 'cat > /my-file.txt' - كتابة النص my text إلى ملف /my-file.txt داخل الحاوية my-container.
cat > /my-file.txt - كتابة محتويات الإدخال القياسي إلى ملف /dev/stdin.
اطبع
$ sed -i "s/app_version = 1/app_version = 2/" uptimer.py
$ docker build --tag uptimer .
Sending build context to Docker daemon 39.94kB
Step 1/4 : FROM python:alpine
---> 8ecf5a48c789
Step 2/4 : EXPOSE 8080
---> Using cache
---> cf92d174c9d3
Step 3/4 : COPY uptimer.py app.py
---> 3eca6a51cb2d
Step 4/4 : CMD [ "python", "-u", "./app.py" ]
---> Running in 8f13c6d3d9e7
Removing intermediate container 8f13c6d3d9e7
---> 1d56897841ec
Successfully built 1d56897841ec
Successfully tagged uptimer:latest
$ docker run --detach --rm --name uptimer_BLUE --network web-gateway uptimer
96932d4ca97a25b1b42d1b5f0ede993b43f95fac3c064262c5c527e16c119e02
$ docker logs uptimer_BLUE
Uptimer v2.0 (loads in 5 sec.) started.
$ docker run --rm --network web-gateway alpine wget -qO- http://uptimer_BLUE:8080
<h2>Uptimer v2.0 is running for 23.9 seconds.</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: signal process started
$ wget -qO- http://localhost
<h2>Uptimer v2.0 is running for 63.4 seconds.</h2>
$ docker rm -f uptimer
uptimer
$ wget -qO- http://localhost
<h2>Uptimer v2.0 is running for 84.8 seconds.</h2>
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
96932d4ca97a uptimer "python -u ./app.py" About a minute ago Up About a minute 8080/tcp uptimer_BLUE
80695a822c19 nginx:alpine "/docker-entrypoint.…" 8 minutes ago Up 8 minutes 0.0.0.0:80->80/tcp reverse-proxy
في هذه المرحلة، يتم إنشاء الصورة مباشرة على الخادم، مما يتطلب وجود مصادر التطبيق هناك، كما يقوم بتحميل الخادم بعمل غير ضروري. والخطوة التالية هي تخصيص مجموعة الصور لجهاز منفصل (على سبيل المثال، لنظام CI) ثم نقلها إلى الخادم.
نقل الصور
لسوء الحظ، لا فائدة من تنزيل صورة من المضيف المحلي إلى المضيف المحلي، لذلك لا يمكن الشعور بهذا القسم إلا إذا كان لديك مضيفان عامل إرساء في متناول اليد. في الحد الأدنى، يبدو مثل هذا:
$ ssh production-server docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
$ docker image save uptimer | ssh production-server 'docker image load'
Loaded image: uptimer:latest
$ ssh production-server docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
uptimer latest 1d56897841ec 5 minutes ago 78.9MB
فريق docker save يقوم بتخزين بيانات الصورة في أرشيف .tar، مما يعني أن وزنها يفوق حوالي 1.5 مرة وزنها في شكل مضغوط. لذلك دعونا نهزها باسم توفير الوقت وحركة المرور:
الآن دعونا نجمع كل ما قمنا به يدويًا في برنامج نصي واحد. لنبدأ بوظيفة المستوى الأعلى، ثم نلقي نظرة على الباقي المستخدم فيها.
تقنيات مثيرة للاهتمام
${parameter?err_msg} - إحدى تعويذات باش السحرية (ويعرف أيضًا باسم استبدال المعلمة). لو parameter لم يتم تعيين، الإخراج err_msg والخروج بالرمز 1.
docker --log-driver journald - افتراضيًا، يكون برنامج تشغيل تسجيل عامل الإرساء عبارة عن ملف نصي بدون أي تدوير. باستخدام هذا الأسلوب، تملأ السجلات القرص بأكمله بسرعة، لذلك بالنسبة لبيئة الإنتاج، تحتاج إلى تغيير برنامج التشغيل إلى برنامج أكثر ذكاءً.
البرنامج النصي للنشر
deploy() {
local usage_msg="Usage: ${FUNCNAME[0]} image_name"
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 "Deploying '$NEW' in place of '$OLD'..."
docker run
--detach
--restart always
--log-driver journald
--name $NEW
--network web-gateway
$image_name || return 3
echo "Container started. Checking health..."
for i in {1..20}
do
sleep 1
if get-service-status $image_name $new_slot
then
echo "New '$NEW' service seems OK. Switching heads..."
sleep 2 # Ensure service is ready
set-active-slot $image_name $new_slot || return 4
echo "'$NEW' service is live!"
sleep 2 # Ensure all requests were processed
echo "Killing '$OLD'..."
docker rm -f $OLD
docker image prune -f
echo "Deployment successful!"
return 0
fi
echo "New '$NEW' service is not ready yet. Waiting ($i)..."
done
echo "New '$NEW' service did not raise, killing it. Failed to deploy T_T"
docker rm -f $NEW
return 5
}
الوظائف المستخدمة:
ensure-reverse-proxy - تأكد من عمل الوكيل العكسي (مفيد للنشر الأول)
get-active-slot service_name - تحديد الفتحة النشطة حاليًا للخدمة المحددة (BLUE أو GREEN)
get-service-status service_name deployment_slot - تحديد ما إذا كانت الخدمة جاهزة لمعالجة الطلبات الواردة
set-active-slot service_name deployment_slot - تغيير تكوين nginx في حاوية الوكيل العكسي
بالترتيب:
ensure-reverse-proxy() {
is-container-up reverse-proxy && return 0
echo "Deploying 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?"Usage: ${FUNCNAME[0]} container_name"}
[ -n "$(docker ps -f name=${container} -q)" ]
return $?
}
get-active-slot() {
local service=${1?"Usage: ${FUNCNAME[0]} service_name"}
if is-container-up ${service}_BLUE && is-container-up ${service}_GREEN; then
echo "Collision detected! Stopping ${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="Usage: ${FUNCNAME[0]} service_name deployment_slot"
local service=${1?usage_msg}
local slot=${2?$usage_msg}
case $service in
# Add specific healthcheck paths for your services here
*) local health_check_port_path=":8080/" ;;
esac
local health_check_address="http://${service}_${slot}${health_check_port_path}"
echo "Requesting '$health_check_address' within the 'web-gateway' docker network:"
docker run --rm --network web-gateway alpine
wget --timeout=1 --quiet --server-response $health_check_address
return $?
}
set-active-slot() {
local usage_msg="Usage: ${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
}
وظيفة get-active-slot يحتاج إلى القليل من الشرح:
لماذا يُرجع رقمًا ولا يُخرج سلسلة؟
على أي حال، في وظيفة الاستدعاء، نتحقق من نتيجة عملها، والتحقق من رمز الخروج باستخدام bash أسهل بكثير من السلسلة. بالإضافة إلى ذلك، الحصول على سلسلة منه أمر بسيط للغاية: get-active-slot service && echo BLUE || echo GREEN.
هل ثلاثة شروط كافية بالضبط للتمييز بين جميع الدول؟
حتى اثنان يكفيان، والأخير هو فقط للاكتمال، حتى لا أكتب else.
تبقى الوظيفة التي تُرجع تكوينات nginx فقط غير محددة: get-nginx-config service_name deployment_slot. قياسا على التحقق من الصحة، يمكنك هنا تعيين أي تكوين لأي خدمة. من المثير للاهتمام - فقط cat <<- EOF، والذي يسمح لك بإزالة كافة علامات التبويب في البداية. صحيح أن سعر التنسيق المعقول هو علامات تبويب مختلطة بمسافات، والتي تعتبر اليوم شكلاً سيئًا للغاية. لكن bash يفرض علامات التبويب، وسيكون من الجيد أيضًا أن يكون لديك تنسيق عادي في تكوين nginx. باختصار، يبدو أن خلط علامات التبويب مع المسافات هو الحل الأفضل من بين الحلول الأسوأ. ومع ذلك، في المقتطف أدناه لن ترى هذا، نظرًا لأن habr "يقوم بذلك بشكل جيد"، مما يؤدي إلى تغيير جميع علامات التبويب إلى 4 مسافات وجعل EOF غير صالح. وهنا يكون ملحوظا.
لكي لا أستيقظ مرتين، سأخبرك على الفور cat << 'EOF'، والتي سيتم مواجهتها لاحقًا. إذا كان من السهل الكتابة cat << EOF، ثم يتم تنفيذ الاستيفاء داخل السلسلة (يتم توسيع المتغيرات ($foo)، مكالمات الأوامر ($(bar))، وما إلى ذلك)، وإذا قمت بتضمين علامة نهاية المستند بين علامات اقتباس مفردة، فسيتم تعطيل الاستيفاء وسيصبح الحرف $ الإخراج كما هو. ما تحتاجه لإدراج برنامج نصي داخل برنامج نصي آخر.
get-nginx-config() {
local usage_msg="Usage: ${FUNCNAME[0]} service_name deployment_slot"
local service=${1?$usage_msg}
local slot=${2?$usage_msg}
[ "$slot" == BLUE ] || [ "$slot" == GREEN ] || return 1
local container_name=${service}_${slot}
case $service in
# Add specific nginx configs for your services here
*) nginx-config-simple-service $container_name:8080 ;;
esac
}
nginx-config-simple-service() {
local usage_msg="Usage: ${FUNCNAME[0]} proxy_pass"
local proxy_pass=${1?$usage_msg}
cat << EOF
server {
listen 80;
location / {
proxy_pass http://$proxy_pass;
}
}
EOF
}
حان الوقت للطرق على الخادم الهدف. هذا الوقت localhost مناسبة تماما:
$ ssh-copy-id localhost
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
himura@localhost's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh 'localhost'"
and check to make sure that only the key(s) you wanted were added.
لقد كتبنا برنامجًا نصيًا للنشر يقوم بتحميل صورة تم إنشاؤها مسبقًا إلى الخادم الهدف واستبدال حاوية الخدمة بسلاسة، ولكن كيفية تنفيذها على جهاز بعيد؟ يحتوي البرنامج النصي على وسائط، لأنه عالمي ويمكنه نشر العديد من الخدمات في وقت واحد تحت وكيل عكسي واحد (يمكن فرز تكوينات nginx حسب عنوان URL الذي ستكون عليه الخدمة). لا يمكن تخزين البرنامج النصي على الخادم، لأنه في هذه الحالة لن نتمكن من تحديثه تلقائيًا (لغرض إصلاح الأخطاء وإضافة خدمات جديدة)، وبشكل عام، الحالة = evil.
الحل 1: استمر في تخزين البرنامج النصي على الخادم، ولكن انسخه في كل مرة scp. ثم الاتصال عن طريق ssh وتنفيذ البرنامج النصي بالوسائط اللازمة.
سلبيات:
عمليتان بدلاً من واحدة
قد لا يكون المكان الذي تنسخ فيه، أو قد لا يكون هناك إمكانية الوصول إليه، أو قد يتم تنفيذ البرنامج النصي في وقت الاستبدال.
يُنصح بالتنظيف بعد نفسك (احذف البرنامج النصي).
بالفعل ثلاث خطوات.
الحل 2:
احتفظ بتعريفات الوظائف فقط في البرنامج النصي ولا تقم بتشغيل أي شيء على الإطلاق
استخدام sed إلحاق استدعاء دالة بالنهاية
أرسل كل هذا مباشرة إلى shh عبر الأنبوب (|)
الايجابيات:
حقا عديمي الجنسية
لا توجد كيانات معيارية
شعور رائع
هنا دعونا فقط بدون Ansible. نعم، لقد تم اكتشاف كل شيء. نعم، دراجة. تعرف على دراجة بسيطة وأنيقة وبسيطة:
$ cat << 'EOF' > 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_SCRIPT
EOF
$ chmod +x deploy.sh
$ ./deploy.sh localhost magic-porridge-pot
Connecting to localhost...
Yay! The 'deploy' function is executing on 'hut' with argument 'magic-porridge-pot'
ومع ذلك، لا يمكننا التأكد من أن المضيف البعيد لديه bash صالح، لذلك نضيف القليل من التحقق في البداية (وهذا بدلاً من ذلك com.shellbang):
if [ "$SHELL" != "/bin/bash" ]
then
echo "The '$SHELL' shell is not supported by 'deploy.sh'. Set a '/bin/bash' shell for '$USER@$HOSTNAME'."
exit 1
fi
والآن أصبح الأمر حقيقيًا:
$ 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
Sending gzipped image 'uptimer' to 'localhost' via ssh...
Loaded image: uptimer:latest
Connecting to 'localhost' via ssh to seamlessly deploy 'uptimer'...
Deploying 'uptimer_GREEN' in place of 'uptimer_BLUE'...
06f5bc70e9c4f930e7b1f826ae2ca2f536023cc01e82c2b97b2c84d68048b18a
Container started. Checking health...
Requesting 'http://uptimer_GREEN:8080/' within the 'web-gateway' docker network:
HTTP/1.0 503 Service Unavailable
wget: server returned error: HTTP/1.0 503 Service Unavailable
New 'uptimer_GREEN' service is not ready yet. Waiting (1)...
Requesting 'http://uptimer_GREEN:8080/' within the 'web-gateway' docker network:
HTTP/1.0 503 Service Unavailable
wget: server returned error: HTTP/1.0 503 Service Unavailable
New 'uptimer_GREEN' service is not ready yet. Waiting (2)...
Requesting 'http://uptimer_GREEN:8080/' within the 'web-gateway' docker network:
HTTP/1.0 200 OK
Server: BaseHTTP/0.6 Python/3.8.3
Date: Sat, 22 Aug 2020 20:15:50 GMT
Content-Type: text/html
New 'uptimer_GREEN' service seems OK. Switching heads...
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
2020/08/22 20:15:54 [notice] 97#97: signal process started
The 'uptimer_GREEN' service is live!
Killing 'uptimer_BLUE'...
uptimer_BLUE
Total reclaimed space: 0B
Deployment successful!
الآن يمكنك فتح http://localhost/ في المستعرض، قم بتشغيل النشر مرة أخرى وتأكد من تشغيله بسلاسة عن طريق تحديث الصفحة الموجودة على القرص المضغوط أثناء النشر.