النشر الأزرق والأخضر عند الحد الأدنى للأجور

في هذه المقالة نستخدم سحق, سه, عامل ميناء и 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 مرة وزنها في شكل مضغوط. لذلك دعونا نهزها باسم توفير الوقت وحركة المرور:

$ docker image save uptimer | gzip | ssh production-server 'zcat | docker image load'
Loaded image: uptimer:latest

ومع ذلك، يمكنك مشاهدة عملية النقل (ومع ذلك، فهذا يتطلب أداة مساعدة تابعة لجهة خارجية):

$ docker image save uptimer | gzip | pv | ssh production-server 'zcat | docker image load'
25,7MiB 0:01:01 [ 425KiB/s] [                   <=>    ]
Loaded image: uptimer:latest

نصيحة: إذا كنت بحاجة إلى الكثير من المعلمات للاتصال بالخادم عبر SSH، فمن المحتمل أنك لا تستخدم الملف ~/.ssh/config.

نقل الصور عبر docker image save/load هي الطريقة الأكثر بساطة، ولكنها ليست الوحيدة. وهناك آخرون:

  1. سجل الحاويات (معيار الصناعة).
  2. اتصل ببرنامج عامل الإرساء الخاص بالخادم من مضيف آخر:
    1. متغيرات البيئة DOCKER_HOST.
    2. خيار سطر الأوامر -H أو --host أداة docker-compose.
    3. docker context

الطريقة الثانية (مع ثلاثة خيارات لتنفيذها) موصوفة جيدًا في المقالة كيفية النشر على مضيفات Docker البعيدة باستخدام docker-compose.

deploy.sh

الآن دعونا نجمع كل ما قمنا به يدويًا في برنامج نصي واحد. لنبدأ بوظيفة المستوى الأعلى، ثم نلقي نظرة على الباقي المستخدم فيها.

تقنيات مثيرة للاهتمام

  • ${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
}

هذا هو السيناريو كله. و حينئذ جوهر مع هذا البرنامج النصي للتحميل عبر wget أو حليقة.

تنفيذ البرامج النصية ذات المعلمات على خادم بعيد

حان الوقت للطرق على الخادم الهدف. هذا الوقت 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/ في المستعرض، قم بتشغيل النشر مرة أخرى وتأكد من تشغيله بسلاسة عن طريق تحديث الصفحة الموجودة على القرص المضغوط أثناء النشر.

لا تنس التنظيف بعد العمل :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

المصدر: www.habr.com