Triển khai Xanh-Xanh với mức lương tối thiểu

Trong bài viết này chúng tôi sử dụng bash, ssh, docker и nginx Chúng tôi sẽ tổ chức một bố cục liền mạch của ứng dụng web. Triển khai xanh xanh là một kỹ thuật cho phép bạn cập nhật ứng dụng ngay lập tức mà không từ chối một yêu cầu nào. Đây là một trong những chiến lược triển khai không có thời gian ngừng hoạt động và phù hợp nhất cho các ứng dụng có một phiên bản nhưng có khả năng tải phiên bản thứ hai, sẵn sàng chạy ở gần đó.

Giả sử bạn có một ứng dụng web mà nhiều khách hàng đang tích cực làm việc và hoàn toàn không có cách nào để ứng dụng đó dừng lại trong vài giây. Và bạn thực sự cần tung ra bản cập nhật thư viện, sửa lỗi hoặc một tính năng mới thú vị. Trong tình huống bình thường, bạn sẽ cần dừng ứng dụng, thay thế nó và khởi động lại. Trong trường hợp docker, trước tiên bạn có thể thay thế nó, sau đó khởi động lại nó, nhưng vẫn sẽ có một khoảng thời gian mà các yêu cầu tới ứng dụng sẽ không được xử lý, vì thông thường ứng dụng sẽ mất một chút thời gian để tải ban đầu. Điều gì sẽ xảy ra nếu nó khởi động nhưng không thể hoạt động? Đây chính là vấn đề, hãy giải quyết nó bằng những phương tiện tối thiểu và tinh tế nhất có thể.

TUYÊN BỐ TỪ CHỐI TRÁCH NHIỆM: Hầu hết bài viết được trình bày ở định dạng thử nghiệm - dưới dạng bản ghi phiên bảng điều khiển. Hy vọng rằng điều này sẽ không quá khó hiểu và mã sẽ tự ghi lại đầy đủ. Về bầu không khí, hãy tưởng tượng rằng đây không chỉ là những đoạn mã mà còn là giấy từ một chiếc máy điện báo “sắt”.

Triển khai Xanh-Xanh với mức lương tối thiểu

Các kỹ thuật thú vị mà Google khó chỉ bằng cách đọc mã được mô tả ở đầu mỗi phần. Nếu còn điều gì chưa rõ, hãy google và kiểm tra. giải thích (may mắn thay, nó hoạt động trở lại do đã bỏ chặn điện tín). Nếu bạn không thể Google bất cứ điều gì, hãy hỏi trong phần bình luận. Tôi sẽ vui lòng thêm vào phần tương ứng “Các kỹ thuật thú vị”.

Hãy bắt đầu

$ mkdir blue-green-deployment && cd $_

Dịch vụ

Hãy tạo một dịch vụ thử nghiệm và đặt nó vào một thùng chứa.

Kỹ thuật thú vị

  • cat << EOF > file-name (Tài liệu ở đây + Chuyển hướng I / O) là một cách để tạo một tệp nhiều dòng bằng một lệnh. Mọi thứ bash đều đọc từ /dev/stdin sau dòng này và trước dòng EOF sẽ được ghi vào file-name.
  • wget -qO- URL (giải thích) — xuất tài liệu nhận được qua HTTP tới /dev/stdout (tương tự curl URL).

In ra

Tôi đặc biệt ngắt đoạn mã để bật tính năng đánh dấu cho Python. Cuối cùng sẽ có một phần khác như thế này. Hãy xem xét rằng ở những nơi này, giấy đã được cắt để gửi đến bộ phận đánh dấu (nơi mã được tô màu bằng tay bằng bút đánh dấu), và sau đó những mảnh này được dán lại.

$ 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

Proxy ngược

Để ứng dụng của chúng ta có thể thay đổi mà không bị chú ý, cần phải có một số thực thể khác phía trước nó sẽ ẩn sự thay thế của nó. Nó có thể là một máy chủ web nginx в chế độ proxy ngược. Một proxy ngược được thiết lập giữa máy khách và ứng dụng. Nó chấp nhận các yêu cầu từ khách hàng và chuyển tiếp chúng đến ứng dụng cũng như chuyển tiếp phản hồi của ứng dụng đến khách hàng.

Ứng dụng và proxy ngược có thể được liên kết bên trong docker bằng cách sử dụng mạng docker. Do đó, vùng chứa ứng dụng thậm chí không cần chuyển tiếp một cổng trên hệ thống máy chủ; điều này cho phép cách ly ứng dụng tối đa khỏi các mối đe dọa bên ngoài.

Nếu proxy ngược tồn tại trên một máy chủ khác, bạn sẽ phải từ bỏ mạng docker và kết nối ứng dụng với proxy ngược thông qua mạng máy chủ, chuyển tiếp cổng ứng dụng tham số --publish, như ở lần khởi động đầu tiên và như với proxy ngược.

Chúng tôi sẽ chạy proxy ngược trên cổng 80, vì đây chính xác là thực thể sẽ lắng nghe mạng bên ngoài. Nếu cổng 80 bận trên máy chủ thử nghiệm của bạn, hãy thay đổi tham số --publish 80:80 trên --publish ANY_FREE_PORT:80.

Kỹ thuật thú vị

In ra

$ 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>

Triển khai liền mạch

Hãy tung ra phiên bản mới của ứng dụng (với hiệu suất khởi động tăng gấp hai lần) và cố gắng triển khai nó một cách liền mạch.

Kỹ thuật thú vị

  • echo 'my text' | docker exec -i my-container sh -c 'cat > /my-file.txt' - Viết văn bản my text nộp /my-file.txt bên trong thùng chứa my-container.
  • cat > /my-file.txt — Ghi nội dung của đầu vào tiêu chuẩn vào một tập tin /dev/stdin.

In ra

$ 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

Ở giai đoạn này, hình ảnh được xây dựng trực tiếp trên máy chủ, yêu cầu phải có nguồn ứng dụng ở đó, đồng thời tải lên máy chủ những công việc không cần thiết. Bước tiếp theo là phân bổ tập hợp hình ảnh cho một máy riêng biệt (ví dụ: cho hệ thống CI) và sau đó chuyển nó đến máy chủ.

Chuyển hình ảnh

Thật không may, việc chuyển hình ảnh từ localhost sang localhost là vô nghĩa, vì vậy phần này chỉ có thể được khám phá nếu bạn có sẵn hai máy chủ có Docker. Tối thiểu nó trông giống như thế này:

$ 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

Đội docker save lưu dữ liệu hình ảnh trong kho lưu trữ .tar, nghĩa là nó nặng hơn khoảng 1.5 lần so với trọng lượng ở dạng nén. Vì vậy, hãy bắt đầu với mục đích tiết kiệm thời gian và lưu lượng truy cập:

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

Bạn cũng có thể theo dõi quá trình tải xuống (mặc dù việc này yêu cầu tiện ích của bên thứ ba):

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

Mẹo: Nếu bạn cần một loạt tham số để kết nối với máy chủ qua SSH, thì có thể bạn đang không sử dụng tệp ~/.ssh/config.

Truyền hình ảnh qua docker image save/load - Đây là phương pháp tối giản nhất nhưng không phải là phương pháp duy nhất. Co nhung nguoi khac:

  1. Đăng ký container (tiêu chuẩn ngành).
  2. Kết nối với máy chủ daemon docker từ máy chủ khác:
    1. biến môi trường DOCKER_HOST.
    2. Tùy chọn dòng lệnh -H hoặc --host công cụ docker-compose.
    3. docker context

Phương pháp thứ hai (với ba tùy chọn để thực hiện) được mô tả rõ trong bài viết Cách triển khai trên máy chủ Docker từ xa bằng docker-compose.

deploy.sh

Bây giờ hãy tập hợp mọi thứ chúng tôi đã làm thủ công vào một tập lệnh. Hãy bắt đầu với hàm cấp cao nhất, sau đó xem xét các hàm khác được sử dụng trong đó.

Kỹ thuật thú vị

  • ${parameter?err_msg} - một trong những phép thuật bash (hay còn gọi là thay thế tham số). Nếu như parameter không được chỉ định, đầu ra err_msg và thoát bằng mã 1.
  • docker --log-driver journald — theo mặc định, trình điều khiển ghi nhật ký docker là một tệp văn bản không có bất kỳ vòng quay nào. Với phương pháp này, nhật ký sẽ nhanh chóng lấp đầy toàn bộ đĩa, vì vậy đối với môi trường sản xuất, cần phải thay đổi trình điều khiển sang trình điều khiển thông minh hơn.

Kịch bản triển khai

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
}

Các tính năng được sử dụng:

  • ensure-reverse-proxy — Đảm bảo proxy ngược đang hoạt động (hữu ích cho lần triển khai đầu tiên)
  • get-active-slot service_name - Xác định vị trí nào hiện đang hoạt động cho một dịch vụ nhất định (BLUE hoặc GREEN)
  • get-service-status service_name deployment_slot - Xác định xem dịch vụ có sẵn sàng xử lý các yêu cầu đến hay không
  • set-active-slot service_name deployment_slot — Thay đổi cấu hình nginx trong vùng chứa proxy ngược

Theo thứ tự:

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
}

Chức năng get-active-slot yêu cầu một chút giải thích:

Tại sao nó trả về một số và không xuất ra một chuỗi?

Dù sao, trong hàm gọi, chúng ta kiểm tra kết quả công việc của nó và kiểm tra mã thoát bằng bash dễ dàng hơn nhiều so với kiểm tra một chuỗi. Ngoài ra, việc lấy một chuỗi từ nó rất đơn giản:
get-active-slot service && echo BLUE || echo GREEN.

Ba điều kiện có thực sự đủ để phân biệt tất cả các trạng thái?

Triển khai Xanh-Xanh với mức lương tối thiểu

Hai cái cũng đủ rồi, cái cuối cùng ở đây chỉ để cho đầy đủ thôi, khỏi phải viết else.

Chỉ có hàm trả về cấu hình nginx vẫn chưa được xác định: get-nginx-config service_name deployment_slot. Bằng cách tương tự với kiểm tra tình trạng, tại đây bạn có thể đặt bất kỳ cấu hình nào cho bất kỳ dịch vụ nào. Trong số những điều thú vị - chỉ cat <<- EOF, cho phép bạn xóa tất cả các tab ngay từ đầu. Đúng, cái giá của một định dạng tốt là các tab kết hợp với khoảng trắng, ngày nay được coi là dạng rất xấu. Nhưng bash buộc các tab và sẽ rất tuyệt nếu có định dạng bình thường trong cấu hình nginx. Nói tóm lại, việc trộn các tab với khoảng trắng ở đây thực sự có vẻ là giải pháp tốt nhất trong số những điều tồi tệ nhất. Tuy nhiên, bạn sẽ không thấy điều này trong đoạn trích bên dưới, vì Habr “làm rất tốt” bằng cách thay đổi tất cả các tab thành 4 dấu cách và làm cho EOF không hợp lệ. Và điều đáng chú ý ở đây.

Để không phải dậy hai lần, tôi sẽ kể ngay cho bạn nghe về cat << 'EOF', điều này sẽ gặp phải sau này. Nếu bạn viết đơn giản cat << EOF, thì bên trong heredoc, chuỗi được nội suy (các biến được mở rộng ($foo), lệnh gọi ($(bar)) v.v.) và nếu bạn đặt phần cuối tài liệu trong dấu ngoặc đơn thì nội suy sẽ bị tắt và ký hiệu $ được hiển thị như cũ. Những gì bạn cần để chèn một tập lệnh vào bên trong một tập lệnh khác.

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
}

Đây là toàn bộ kịch bản. Và vì thế ý chính với kịch bản này để tải xuống qua wget hoặc cuộn tròn.

Thực thi các tập lệnh được tham số hóa trên máy chủ từ xa

Đã đến lúc gõ cửa máy chủ mục tiêu. Thời gian này localhost khá phù hợp:

$ 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.

Chúng tôi đã viết một tập lệnh triển khai tải hình ảnh dựng sẵn xuống máy chủ mục tiêu và thay thế bộ chứa dịch vụ một cách liền mạch, nhưng làm cách nào chúng tôi có thể thực thi nó trên máy từ xa? Tập lệnh có các đối số, vì nó phổ biến và có thể triển khai một số dịch vụ cùng lúc dưới một proxy ngược (bạn có thể sử dụng cấu hình nginx để xác định url nào sẽ là dịch vụ nào). Tập lệnh không thể được lưu trữ trên máy chủ, vì trong trường hợp này, chúng tôi sẽ không thể cập nhật nó tự động (với mục đích sửa lỗi và thêm dịch vụ mới) và nói chung, state = evil.

Giải pháp 1: Vẫn lưu trữ tập lệnh trên máy chủ nhưng sao chép nó mỗi lần qua scp. Sau đó kết nối qua ssh và thực thi tập lệnh với các đối số cần thiết.

Nhược điểm:

  • Hai hành động thay vì một
  • Có thể không có nơi bạn sao chép hoặc có thể không có quyền truy cập vào nó hoặc tập lệnh có thể được thực thi tại thời điểm thay thế.
  • Bạn nên tự mình dọn dẹp (xóa tập lệnh).
  • Đã có ba hành động.

Giải pháp 2:

  • Chỉ giữ lại các định nghĩa hàm trong tập lệnh và không chạy gì cả
  • Với sed thêm lệnh gọi hàm vào cuối
  • Gửi tất cả trực tiếp đến shh qua đường ống (|)

Ưu điểm:

  • Thực sự không có quốc tịch
  • Không có thực thể soạn sẵn nào
  • Cảm giác mát mẻ

Hãy cứ làm điều đó mà không cần Ansible. Vâng, mọi thứ đã được phát minh. Vâng, một chiếc xe đạp. Hãy nhìn chiếc xe đạp đơn giản, thanh lịch và tối giản như thế nào:

$ 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'

Tuy nhiên, chúng tôi không thể chắc chắn rằng máy chủ từ xa có đầy đủ bash hay không, vì vậy chúng tôi sẽ thêm một kiểm tra nhỏ ngay từ đầu (điều này thay vì tiếng nổ):

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

Và bây giờ nó là sự thật:

$ 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!

Bây giờ bạn có thể mở http://localhost/ trong trình duyệt, hãy chạy lại quá trình triển khai và đảm bảo rằng nó chạy trơn tru bằng cách cập nhật trang theo CD trong quá trình bố trí.

Đừng quên dọn dẹp sau giờ làm việc nhé :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

Nguồn: www.habr.com