VPGAME 的 Kubernetes 遷移實踐
導讀:VPGAME 是集賽事運營、媒體資訊、大資料分析、玩家社群、遊戲周邊等為一體的綜合電競服務平臺。總部位於中國杭州,在上海和美國西雅圖分別設立了電競大資料研發中心和 AI 研發中心。本文將講述 VPGAME 將伺服器遷移至 Kubernetes 的過程。
背景
隨著容器技術的日趨成熟,公司近期計劃將服務遷移至容器環境,透過 Kubernetes 對容器進行排程、編排和管理。並藉此機會,對服務進行標準化,最佳化整個 CI/CD 的流程,提高服務部署的效率。
CI/CD 工具的選擇
CI/CD 工具上,我們選擇了 GitLab-CI。GitLab-CI 就是一套配合 GitLab 使用的持續整合系統,以完成程式碼提交之後的安裝依賴、編譯、單元測試、lint、映象構建以及釋出等工作。
GitLab-CI 完美地和 GitLab 進行整合,在使用的時候只需要安裝配置 gitlab-runner 即可。GitLab-Runner 在向 GitLab 完成註冊後可以提供進行 CI/CD 操作的環境,負責從 GitLab 中拉取程式碼,根據程式碼倉庫中配置的 gitlab-ci.yml ,執行相應的命令進行 CI/CD 工作。
相比於 Jenkins,GitLab-CI 配置簡單,只需在工程中配置 gitlab-ci.yml 檔案完成 CI/CD 流程的編寫,不需要像在 Jenkins 裡一樣配置 webhook 回撥地址,也不需要 Jenkins 新建這個專案的編譯配置。並且個人認為 GitLab 的 CI/CD 過程顯示比 Jenkins 更加美觀。當然 Jenkins 依靠它豐富的外掛,可以配置很多 GitLab-CI 不存在的功能。按照現在我們的需求, GitLab-CI 簡單易用,在功能也滿足我們的需求。
服務執行環境
容器環境優點
傳統的服務部署方式是在作業系統中安裝好相應的應用依賴,然後進行應用服務的安裝,這種部署方式的缺點是將服務的程式、配置、依賴庫以及生命週期與宿主機作業系統緊密地耦合在一起,對服務的升級、擴縮容、遷移等操作不是十分便利。
容器的部署方式則是以映象為核心,在程式碼進行編譯構建時,將應用程式與應用程式執行所需要的依賴打包成一個映象,在部署階段,透過映象建立容器例項完成服務的部署和執行。從而實現以應用為中心的管理,容器的隔離性實現了資源的隔離,由於容器不需要依賴宿主機的作業系統環境,所以可以很好地保證開發、測試和生產環境的一致性。此外,由於構建好的映象是不可變的,並且可以透過 tag 進行版本控制,所以可以提供可靠、頻繁的容器映象構建和部署,亦可方便及快速進行回滾操作。
Kubernetes 平臺功能
Kubernetes(簡稱 k8s),作為一個容器排程、編排和管理平臺,可以在物理或虛擬機器叢集上排程和執行應用程式容器,提供了一個以容器為核心的基礎架構。透過 Kubernetes,對容器進行編排和管理,可以:
- 快速、可預測地部署服務
- 擁有即時擴充套件服務的能力
- 滾動升級,完成新功能釋出
- 最佳化硬體資源,降低成本
阿里雲容器服務優勢
我們在服務遷移中選用了阿里雲的容器服務,它基於原生 Kubernetes 進行適配和增強,簡化叢集的搭建和擴容等工作,整合阿里雲虛擬化、儲存、網路和安全能力,打造雲端最佳的 Kubernetes 容器化應用執行環境。在便捷性上,可以透過 Web 介面一鍵完成 Kubernetes 叢集的建立、升級以及節點的擴縮容。功能上,在網路、儲存、負載均衡和監控方面與阿里雲資源整合,在遷移過程中可以最小化減少遷移帶來的影響。
此外,在選擇叢集建立時,我們選擇了託管版 Kubernetes,只需建立 Worker 節點,Master 節點由容器服務建立並託管。如此一來,我們在 Worker 節點的規劃與資源隔離上還是具備自主性和靈活性的同時不需要運維管理 Kubernetes 叢集 Master 節點,可以將更多的精力關注在應用服務上。
GitLab Runner 部署
GitLab CI 工作流程
GitLab CI 基本概念
在介紹 GitLab CI 之前,首先簡單介紹一下 GitLab CI 裡的一些基本概念,具體如下:
- Pipeline:Gitlab CI 裡的流水線,每一次程式碼的提交觸發 GitLab CI 都會產生一個 Pipeline;
- Stage:每個 Pipeline 由多個 Stage 組成,並且每個 Stage 是有先後順序的;
- Job:GitLab CI 裡的最小任務單元,負責完成具有一件事情,例如編譯、測試、構建映象等。每個 Job 都需要指定 Stage ,所以 Job 的執行順序可以透過制定不同的 Stage 來實現;
- GitLab Runner:是具體執行 Job 的環境,每個 Runner 在同一時間只能執行一個 Job;
- Executor:每個 Runner 在向 GitLab 註冊的時候需要指定 Executor,來決定透過何種型別的執行器來完成 Job。
GitLab CI 的工作流程
當有程式碼 push 到 GitLab 時,就會觸發一個 Pipeline。然後進行編譯,測試和映象構建等操作等操作,其中每一步操作都為一個 Job。在 CD 階段,會將 CI 階段構建出來的結果根據情況部署到測試環境或生產環境。
GitLab Runner 介紹
Gitlab Runner 分類
GitLab 中有三種型別的 Runner ,分別為:
- shared:所有專案使用
- group:group下專案使用
- specific:指定專案使用
我們可以根據需要向 GitLab 註冊不同型別的 Runner,註冊的方式是相同的。
Gitlab Runner 工作過程
Runner 首先會向 GitLab 發起註冊請求,請求內容中包含 token、tag 等資訊,註冊成功後 GitLab 會向 Runner 返回一個 token,後續的請求,Runner 都會攜帶這個請求。
註冊成功後,Runner 就會不停的向 GitLab 請求 Job,時間間隔是 3s。若沒有請求到 Job,GitLab 返回 204 No Content。如果請求到 Job,GitLab 會把 Job 資訊返回回來,Runner 在接收到 Job 之後,會向 GitLab 傳送一個確認請求,同時更新任務的狀態。之後,Runner 開始 Job 的執行, 並且會定時地將中間資料,以 Patch 請求的方式傳送給 GitLab。
GitLab Runner 的 Executor
Runner 在實際執行 Job 時,是透過呼叫 Executor 來完成的。Runner 在註冊時提供了 SSH、Shell、Docker、docker-ssh、VirtualBox、Kubernetes 等不同型別的 Executor 來滿足不同的場景和需求。
其中我們常用的有 Shell 和 Docker 等 Executor,Shell 型別主要是利用 Runner 所在主機的環境進行 Job的執行。而 Docker 型別的 Executor 在每個 Job 開始時,拉取映象生產一個容器,在容器裡完成 Job,在 Job 完成後,對應的容器就會被銷燬。由於 Docker 隔離性強、輕量且回收,我們在使用時選用 Docker 型別的 Executor 去執行 Job,我們只要提前做好 Job 所需環境的 Docker 映象,在每個 Job 定義好 image 即可使用對應的環境,操作便捷。
GitLab Runner 安裝與配置
Docker 安裝
由於我們需要使用 Docker 型別的 Executor,所以需要在執行 Runnner 的伺服器上先安裝 Docker,具體步驟如下(CentOS 環境):
安裝需要的軟體包,yum-util 提供 yum-config-manager 功能,另外兩個是 DeviceMapper 驅動依賴:
yum install -y yum-utils device-mapper-persistent-data lvm2
設定 yum 源:
yum-config-manager --add-repo
安裝 Docker:
yum install docker-ce -y
啟動並加入開機啟動:
systemctl start docker
systemctl enable docker
Gitlab runner 安裝與啟動
執行下面的命令進行 GitLab Runner 的安裝和啟動:
curl -L | sudo bash
sudo yum install gitlab-runner -y
gitlab-runner start
GitLab Runner 註冊與配置更新
啟動 GitLab Runner 後還需要向 GitLab 進行註冊,在註冊前需要從 GitLab 裡查詢 token。不同型別的 Runner 對應的 token 獲取的路徑不同。shared Runner 需要 admin 許可權的賬號,按如下方式可以獲取對應的 token。
其他兩種型別在對應的頁面下( group 或 project 主頁)的 setting—>CI/CD—>Runner 可以獲取到 token。
Runner 的註冊方式分為互動式和非互動式兩種。其中互動式註冊方式,在輸入 gitlab-runner register 命令後,按照提示輸入註冊所需要的資訊,包括 gitlab url、token 和 Runner 名字等。這邊個人比較推薦非互動式命令,可以事先準備好命令,完成一鍵註冊,並且非互動式註冊方式提供了更多的註冊選項,可以滿足更多樣化的需求。
按如下示例即可完成一個 Runner 的註冊:
gitlab-runner register --non-interactive \
--url "\
--registration-token "xxxxxxxxxxx" \
--executor "docker" \
--docker-image alpine:latest \
--description "base-runner-docker" \
--tag-list "base-runner" \
--run-untagged="true" \
--docker-privileged="true" \
--docker-pull-policy "if-not-present" \
--docker-volumes /etc/docker/daemon.json:/etc/docker/daemon.json \
--docker-volumes /etc/gitlab-runner/key/docker-config.json:/root/.docker/config.json \
--docker-volumes /etc/gitlab-runner/find_diff_files:/usr/bin/find_diff_files \
--docker-volumes /etc/gitlab-runner/key/id_rsa:/root/.ssh/id_rsa \
--docker-volumes /etc/gitlab-runner/key/test-kube-config:/root/.kube/config
我們可以透過 --docker-pull-policy 指定 Executor 執行 Job 時 Dokcer 映象下載策略。--docker-volumes 指定容器與宿主機(即 Runner 執行的伺服器)的檔案掛載對映關係。上面掛載的檔案主要是用於 Runner 在執行 Job 時,運用的一些 key,包括訪問 GitLab、Docker Harbor 和 Kubernetes 叢集的 key。當然,如果還有其他檔案需要共享給容器,可以透過 --docker-volumes 去指定。
/etc/docker/daemon.json 檔案主要為了可以以 http 方式訪問 docker horbor 所做的設定:
{ "insecure-registries" : ["] }
完成註冊後,重啟 Runner 即可:
gitlab-runner restart
部署完成後,可以在 GitLab 的 Web 管理介面檢視到不同 Runner 的資訊。
此外,如果一臺服務需要註冊多個 Runner ,可以修改 /etc/gitlab-runner/config.toml 中的 concurrent 值增加 Runner 的併發數,修改完之後同樣需要重啟 Runner。
Docker 基礎映象製作
為了滿足不同服務對執行環境的多樣化需求,我們需要為不同語言的服務提前準備不同的基礎映象用於構建映象階段使用。此外,CI/CD 所需要的工具映象也需要製作,作為 Runner 執行 Job 時生成容器所需要的 Docker 映象。
所有的映象都以編寫 Dockerfile 的形式透過 GitLab 進行管理,並且我們編寫了 .gitlab-ci.yml 檔案,使得每次有 Dockerfile 新增或者修改就會觸發 Pipeline 進行映象的構建並上傳到 Harbor 上。這種管理方式有以下優點:
- 按照一定規則自動構建映象,可以快速便捷地新建和更新映象
- 根據規則可以找到映象對應的 Dockerfile,明確映象的具體組成
- 團隊成員可以透過提交 Merge Request 自由地構建自己需要的映象
映象分類
- 執行時基礎映象:提供各個語言執行時必須的工具和相應的 package。
- CI 映象:基於執行時基礎映象,新增單元測試、lint、靜態分析等功能,用在 CI/CD 流程中的 test 環節。
- 打包上線映象:用在 CI/CD 流程中的 build 和 deploy 環節。
Dockerfile 目錄結構
每個資料夾都有 Dockerfile 來描述映象的基本情況,其中包含了 Java、PHP、Node 和 Go 等不同語言的執行時基礎映象和 CI 映象,還有 docker-kubectl 這類工具映象的 Dockerfile。
以 PHP 映象為例:
php/
├── 1.0
│ ├── Dockerfile
│ ├── ci-1.0
│ │ └── Dockerfile
│ ├── php.ini
│ ├── read-zk-config
│ ├── start_service.sh
│ └──
└── nginx
├── Dockerfile
├── api.vpgame.com.conf
└── nginx.conf
該目錄下有一個名為 1.0 的資料夾,裡面有一個 Dockerfile 用來構建 php fpm 執行時基礎進行映象。主要是在 php:7.1.16-fpm-alpine3.4 加了我們自己定製化的檔案,並指定工作目錄和容器初始命令。
FROM php:7.1.16-fpm-alpine3.4
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories\
&& apk upgrade --update && apk add --no-cache --virtual build-dependencies $PHPIZE_DEPS \
tzdata postgresql-dev libxml2-dev libmcrypt libmcrypt-dev libmemcached-dev cyrus-sasl-dev autoconf \
&& apk add --no-cache freetype libpng libjpeg-turbo freetype-dev libpng-dev libjpeg-turbo-dev libmemcached-dev \
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone \
&& docker-php-ext-configure gd \
--with-gd \
--with-freetype-dir=/usr/include/ \
--with-png-dir=/usr/include/ \
--with-jpeg-dir=/usr/include/ \
&& docker-php-ext-install gd pdo pdo_mysql bcmath opcache \
&& pecl install memcached apcu redis \
&& docker-php-ext-enable memcached apcu redis \
&& apk del build-dependencies \
&& apk del tzdata \
&& rm -rf /var/cache/apk/* \
&& rm -rf /tmp/* \
&& rm -rf /working/* \
&& rm -rf /usr/local/etc/php-fpm.d/*
COPY start_service.sh /usr/local/bin/start_service.sh
COPY read-zk-config /usr/local/bin/read-zk-config
COPY php.ini /usr/local/etc/php/php.ini
COPY /usr/local/etc/php-fpm.d/
WORKDIR /work
CMD ["start_service.sh"]
在 1.0/ci-1.0 還有一個 Dockerfile,是用來構建 PHP 在進行單元測試和 lint 操作時所使用的 CI 映象。可以看到它基於上面的基礎執行時映象增加其他工具來進行構建的。
FROM docker.vpgame.cn/infra/php-1.0
ENV PATH="/root/.composer/vendor/bin:${PATH}"
ENV COMPOSER_ALLOW_SUPERUSER=1
RUN mkdir -p /etc/ssh && echo "StrictHostKeyChecking no" >> /etc/ssh/ssh_config
RUN apk --update add --no-cache make libc-dev autoconf gcc openssh-client git bash &&\
echo "apc.enable_cli=1" >> /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini
RUN pecl install xdebug && docker-php-ext-enable xdebug &&\
echo -e "\nzend_extension=xdebug.so" >> /usr/local/etc/php/php.ini
RUN wget \
chmod +x /bin/composer && \
composer config -g -q repo.packagist composer
RUN composer global require -q phpunit/phpunit:~5.0 squizlabs/php_codesniffer:~3.0
WORKDIR /
CMD ["/bin/bash"]
另外 Nginx 目錄下同樣有 Dockerfile,來定製化我們 PHP 專案所需要的 Nginx 映象。
在 GitLab 裡第一次增加新的 Dockerfile 或者更改 Dockerfile 時,會觸動 Pipeline 自動進行映象的構建並上傳的我們私有的 Docker Harbor 上。
映象自動構建基本原理
由於各個映象透過 Dockerfile 進行管理, Master 分支有新的合併,可以透過 git diff 命令找出合併前後新增或更新的 Dockerfile,然後根據這些 Dockerfile 依據一定的命名規則構建映象,並上傳到 Docker Harbor 上。
for FILE in `bash ./find_diff_files|grep Dockerfile|sort`;
do
DIR=`dirname "$FILE"`;
IMAGE_NAME=`echo $DIR | sed -e 's/\//-/g'`;
echo $CI_REGISTRY/$HARBOR_DIR/$IMAGE_NAME;
docker build -t $CI_REGISTRY/$HARBOR_DIR/$IMAGE_NAME -f $FILE $DIR;
docker push $CI_REGISTRY/$HARBOR_DIR/$IMAGE_NAME;
done
上面命令中 finddifffiles 基於 git diff 命令找出合併前後有差異的檔案。
加速 tips
- Alpine Linux Package Management(APK)映象地址: ;
- 一些海外軟體下載會比較慢,可以先下載下來上傳至阿里雲 OSS 後下載。Dockerfile 使用阿里雲 OSS 作為下載源,減少構建映象時間。
基於 .gitlab-ci.yml 的 CI/CD 流程
在完成 GitLab Runner 以及 Docker 基礎映象的製作之後,我們便可以進行 CI/CD 流程來完成程式碼更新之後的單元測試、lint、編譯、映象打包以及部署等工作。透過 GitLab CI 進行 CI/CD 的操作只需要在程式碼倉庫裡編輯和維護一個 .gitlab-ci.yml 檔案,每當程式碼有更新,GitLab CI 會讀取 .gitlab-ci.yml 裡的內容,生成一條 Pipeline 進行 CI/CD 的操作。
.gitlab-ci.yml 的語法比較簡單,基於 yaml 語法進行 Job 的描述。我們把 CI/CD 流程中所需要完成的任務拆分成檔案裡的 Job,只要對每個 Job 完成清晰的定義,便可形成一套合適高效並具有普適性的 CI/CD 流程。
定義 stages
stages 是一個非常重要的概念, 在 .gitlab-ci.yml 中進行全域性定義, 在定義 Job 時指定其中的值來表明 Job 所處的 stage。而在 stages 裡元素的順序定義了 Job 的執行順序:所有在相同 stage 的 Job 會並行執行,只有當前 stage 的所有成功完成後,後面 stage 的 Job 才會去執行。
例如,定義如下 stages:
stages:
- build
- test
- deploy
- 首先,所有 build 裡的 Job 會並行執行;
- 當 build 裡所有 Job 執行成功, test 裡所有 Job 會並行執行;
- 如果 test 裡所有 Job 執行成功, deploy 裡所有 Job 會並行執行;
- 如果 deploy 裡所有 Job 執行成功, 當前 Pipeline 會被標記為 passed;
- 當某個 stage 的 Job 執行失敗, Pipeline 會標記為為 failed,其後續stage 的 Job 都不會被執行。
Job 的描述
Job 是 .gitlab-ci.yml 檔案中最重要的組成部分,所有的 CI/CD 流程中所執行的任務均可以需要透過定義 Job 來實現。具體來說,我們可以透過關鍵字來對每一個 Job 進行描述。由於 Job 中的關鍵字眾多,並且用法比較豐富,這邊針對我們自己實戰中的一個 Job 來進行說明。
unittest:
stage: test
image: docker.vpgame.cn/infra/php-1.0-ci-1.1
services:
- name: docker.vpgame.cn/infra/mysql-5.6-multi
alias: mysql
- name: redis:4.0
alias: redis_default
script:
- mv .env.tp .env
- composer install --no-dev
- phpunit -v --coverage-text --colors=never --coverage-html=coverage --stderr
artifacts:
when: on_success
paths:
- vendor/
- coverage/
expire_in: 1 hour
coverage: '/^\s*Lines:\s*\d+.\d+\%/'
only:
- branches
- tags
tags:
- base-runner
上面的 Job 主要完成了單元測試的功能,在起始行定義了 Job 的名稱。下面我們來解釋 Job 每個關鍵字的具體含義。
- stage,定義了 Job 所處的 stage,值為定義在全域性中 stages 裡的值;
- image,指定了 Runner 執行所需要的映象,這個映象是我們之前製作的基本映象。透過該映象執行的 Docker 即是 Job 執行的環境;
- services,Runner 所執行的 Docker 所需要的連線依賴,在這邊分別定義了 MySQL 和 Redis,在 Job 執行時會去連線這兩個映象生成的 Docker;
- script,Job 執行的具體的命令 ,透過 Shell 來描述。此 Job 中的 script 主要完成了程式碼的編譯和單元測試;
- artifacts,主要是將此 Job 中完成的結果打包儲存下來,可以透過 when 指定何時儲存,path 定義了儲存的檔案路徑, expire_in 指定了結果儲存的有效期。與之對應的是 dependencies 引數,如果其他 Job 需要此 Job 的 artifacts ,只需要在 Job 按照如下定義即可;
dependencies:
- unittest
- only 關鍵字指定了 Job 觸發的時機,該例子中說明只有分支合併或者打 tag 的情況下,該 Job 才會被觸發;
- 與 only 相對還有 except 關鍵字來排除觸發 Job 某些情況。此外 only 還支援正規表示式,比如;
job:
only:
- /^issue-.*$/
except:
- branches
這個例子中,只有以 issue- 開頭 tag 標記才會觸發 Job。如果不加 except 引數,以 issue- 開頭的分支或者 tag 標記會觸發 Job。
- tags,tags關鍵字主要是用來指定執行的 Runner 型別。在我們實際運用中,部署測試環境和生產環境所採用的 Runner 是不一樣的,它們是透過不同的 tag 去標識區分。
所以,我們在 Job 定義中,透過 tags 指定 Runner 的值,來指定所需要的 Runner。
我們可以看到 Job 的定義非常的清晰和靈活,關於 Job 的使用遠不止這些功能,更詳細的用法可以參考 GitLab CI/CD 官方文件。
CI/CD 流程編排
在清楚瞭如何描述一個 Job 之後,我們透過定義一個個 Job,並進行編排形成 Pipelines。因為我們可以描述設定 Job 的觸發條件,所以透過不同的條件可以觸發形成不一樣的 Pipelines。
在 PHP 專案 Kubernetes 上線過程中,我們規定了合併 Master 分支會進行 lint、unitest、build-test 以及 deploy-test 四個 Job。
在測試環境驗證透過之後,我們再透過打 tag 進行正式環境的上線。此處的 Pipelines 包含了 unittest、build-pro 和 deploy-pro 三個 Job。
在 .gitlab-ci.yml 檔案中,test 階段主要完成 lint 和 unitest 兩個 Job,不同的語言在進行 Job 定義時會各有不同。我們重點來看一下 build 和 deploy 兩個 stage 的 Job 描述。build stage:
# Build stage
.build-op:
stage: build
dependencies:
- unittest
image: docker.vpgame.cn/infra/docker-kubectl-1.0
services:
- name: docker:dind
entrypoint: ["dockerd-entrypoint.sh"]
script:
- echo "Image name:" ${DOCKER_IMAGE_NAME}
- docker build -t ${DOCKER_IMAGE_NAME} .
- docker push ${DOCKER_IMAGE_NAME}
tags:
- base-runner
build-test:
extends: .build-op
variables:
DOCKER_IMAGE_NAME: ${DOCKER_REGISTRY_PREFIX}/${CI_PROJECT_PATH}:${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA}
only:
- /^testing/
- master
build-prod:
extends: .build-op
variables:
DOCKER_IMAGE_NAME: ${DOCKER_REGISTRY_PREFIX}/${CI_PROJECT_PATH}:${CI_COMMIT_TAG}
only:
- tags
在這邊,由於 build 階段中測試環境和生產環境進行映象打包時基本操作時是相同的,都是根據 Dockerfile 進行映象的 build 和映象倉庫的上傳。這裡用到了一個 extend 引數,可以減少重複的 Job 描述,使得描述更加地簡潔清晰。
我們先定義一個 .build-op 的 Job,然後 build-test 和 build-prod 都透過 extend 進行繼承,可以透過定義關鍵字來新增或覆蓋 .build-op 中的配置。比如 build-prod 重新定義了變數( variables)DOCKER_IMAGE_NAME以及觸發條件(only)更改為了打 tag 。
這邊我們還需要注意到的是在定義 DOCKER_IMAGE_NAME 時,我們引用了 GitLab CI 自身的一些變數,比如 CI_COMMIT_TAG 表示專案的 commit 的 tag 名稱。我們在定義 Job 變數時,可能會引用到一些 GitLab CI 自身變數,關於這些變數的說明可以參考 GitLab CI/CD Variables 中文文件。
deploy stage:
# Deploy stage
.deploy-op:
stage: deploy
image: docker.vpgame.cn/infra/docker-kubectl-1.0
script:
- echo "Image name:" ${DOCKER_IMAGE_NAME}
- echo ${APP_NAME}
- sed -i "s~__NAMESPACE__~${NAMESPACE}~g" deployment.yml service.yml
- sed -i "s~__APP_NAME__~${APP_NAME}~g" deployment.yml service.yml
- sed -i "s~__PROJECT_NAME__~${CI_PROJECT_NAME}~g" deployment.yml
- sed -i "s~__PROJECT_NAMESPACE__~${CI_PROJECT_NAMESPACE}~g" deployment.yml
- sed -i "s~__GROUP_NAME__~${GROUP_NAME}~g" deployment.yml
- sed -i "s~__VERSION__~${VERSION}~g" deployment.yml
- sed -i "s~__REPLICAS__~${REPLICAS}~g" deployment.yml
- kubectl apply -f deployment.yml
- kubectl apply -f service.yml
- kubectl rollout status -f deployment.yml
- kubectl get all,ing -l app=${APP_NAME} -n $NAMESPACE
# Deploy test environment
deploy-test:
variables:
REPLICAS: 2
VERSION: ${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA}
extends: .deploy-op
environment:
name: test
url:
only:
- /^testing/
- master
tags:
- base-runner
# Deploy prod environment
deploy-prod:
variables:
REPLICAS: 3
VERSION: ${CI_COMMIT_TAG}
extends: .deploy-op
environment:
name: prod
url:
only:
- tags
tags:
- pro-deploy
與 build 階段類似,先先定義一個 .deploy-op 的 Job,然後 deploy-test 和 deploy-prod 都透過 extend 進行繼承。
.deploy-op 主要完成了對 Kubernetes Deployment 和 Service 模板檔案的一些變數的替換,以及根據生成的 Deployment 和 Service 檔案進行 Kubernetes 服務的部署。
deploy-test 和 deploy-prod 兩個 Job 定義了不同變數(variables)以及觸發條件(only)。除此之外, deploy-prod 透過 tags 關鍵字來使用不同的 Runner,將部署的目標叢集指向給生產環境的 Kubernetes。
這裡還有一個關鍵字 environment 需要特別說明,在定義了 environment 之後,我們可以在 GitLab 中檢視每次部署的一些資訊。除了檢視每次部署的一些資訊之外,我們還可以很方便地進行重新部署和回滾。
可以看到,透過對 Job 的關鍵字進行配置,我們可以靈活地編排出我們所需要的 CI/CD 流程,非常好地滿足多樣化的場景。
Deployment 與 Service 配置
在 CI/CD 流程中完成 Docker 映象的打包任務之後需要將服務所對應的映象部署到 Kubernetes 叢集中。Kubernetes 提供了多種可以編排排程的資源物件。首先,我們簡單瞭解一下 Kubernetes 中的一些基本資源。
Kubernetes 基本資源物件概覽
Pod
Pod 作為無狀態應用的執行實體是其中最常用的一種資源物件,Kubernetes 中資源排程最小的基本單元,它包含一個或多個緊密聯絡的容器。這些容器共享儲存、網路和名稱空間,以及如何執行的規範。
在 Kubernetes 中,Pod 是非持久的,會因為節點故障或者網路不通等情況而被銷燬和重建。所以我們在 Kubernetes 中一般不會直接建立一個獨立的 Pod,而是透過多個 Pod 對外提供服務。
ReplicaSet
ReplicaSet 是 Kubernetes 中的一種副本控制器,控制由其管理的 Pod,使 Pod 副本的數量維持在預設的個數。ReplicaSets 可以獨立使用,但是在大多數場景下被 Deployments 作為協調 Pod 建立,刪除和更新的機制。
Deployment
Deployment 為 Pod 和 ReplicaSet 提供了一個宣告式定義方法。透過在 Deployment 中進行目標狀態的描述,Deployment controller 會將 Pod 和 ReplicaSet 的實際狀態改變為所設定的目標狀態。Deployment 典型的應用場景包括:
- 定義 Deployment 來建立 Pod 和 ReplicaSet
- 滾動升級和回滾應用
- 擴容和縮容
- 暫停和繼續 Deployment
Service
在 Kubernetes 中,Pod 會被隨時建立或銷燬,每個 Pod 都有自己的 IP,這些 IP 也無法持久存在,所以需要 Service 來提供服務發現和負載均衡能力。
Service 是一個定義了一組 Pod 的策略的抽象,透過 Label Selector 來確定後端訪問的 Pod,從而為客戶端訪問服務提供了一個入口。每個 Service 會對應一個叢集內部的 ClusterIP,叢集內部可以透過 ClusterIP 訪問一個服務。如果需要對叢集外部提供服務,可以透過 NodePort 或 LoadBalancer 方式。
deployment.yml 配置
deployment.yml 檔案用來定義 Deployment。首先透過一個簡單的 deployment.yml 配置檔案熟悉 Deployment 的配置格式。
上圖中 deployment.yml 分為 8 個部分,分別如下:
- apiVersion 為當前配置格式的版本;
- kind 指定了資源型別,這邊當然是 Deployment;
- metadata 是該資源的後設資料,其中 name 是必需的資料項,還可以指定 label 給資源加上標籤;
- spec 部分是該 Deployment 的規格說明;
- spec.replicas 指定了 Pod 的副本數量;
- spec.template 定義 Pod 的基本資訊,透過 spec.template.metadata 和 spec.template.spec 指定;
- spec.template.metadata 定義 Pod 的後設資料。至少需要定義一個 label 用於 Service 識別轉發的 Pod, label 是透過 key-value 形式指定的;
- spec.template.spec 描述 Pod 的規格,此部分定義 Pod 中每一個容器的屬性,name 和 image 是必需項。
在實際應用中,還有更多靈活個性化的配置。我們在 Kubernetes 的部署實踐中制定了相關的規範,在以上基礎結構上進行配置,得到滿足我們實際需求的 deployment.yml 配置檔案。
在 Kubernetes 的遷移實踐中,我們主要在以下方面對 Deployment 的配置進行了規範的約定:
檔案模板化
首先我們的 deployment.yml 配置檔案是帶有變數的模版檔案,如下所示:
apiVersion: apps/v1beta2
kind: Deployment
metadata:
labels:
app: __APP_NAME__
group: __GROUP_NAME__
name: __APP_NAME__
namespace: __NAMESPACE__
APPNAME__、__GROUPNAME 和 __NAMESPACE__ 這種形式的變數都是在 CI/CD 流程中會被替換成 GitLab 每個 project 所對應的變數,目的是為了多了 project 用相同的 deployment.yml 檔案,以便在進行 Kubernetes 遷移時可以快速複製,提高效率。
服務名稱
- Kubernetes 中執行的 Service 以及 Deployment 名稱由 GitLab 中的 groupname 和 projectname 組成,即 {{groupname}}-{{projectname}},例:microservice-common;
此名稱記為 app_name,作為每個服務在 Kubernetes 中的唯一標識。這些變數可以透過 GitLab-CI 的內建變數中進行獲取,無需對每個 project 進行特殊的配置。 - Lables 中用於識別服務的標籤與 Deployment 名稱保持一致,統一設定為 app:{{app_name}}。
資源分配
節點配置策略,以專案組作為各專案 Pod 執行在哪些 Node 節點的依據,屬於同一專案組的專案的 Pod 執行在同一批 Node 節點。具體操作方式為給每個 Node 節點打上形如 group:__GROUP_NAME__ 的標籤,在 deployment.yml 檔案中做如下設定進行 Pod 的 Node 節點選擇:
...
spec:
...
template:
...
spec:
...
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: group
operator: In
values:
- __GROUP_NAME__
...
資源請求大小,對於一些重要的線上應用,limit 和 request 設定一致,資源不足時 Kubernetes 會優先保證這些 Pod 正常執行。為了提高資源利用率。對一些非核心,並且資源不長期佔用的應用,可以適當減少 Pod 的 request,這樣 Pod 在排程時可以被分配到資源不是十分充裕的節點,提高使用率。但是當節點的資源不足時,也會優先被驅逐或被 oom kill。
健康檢查(Liveness/Readiness)配置
Liveness 主要用於探測容器是否存活,若監控檢查失敗會對容器進行重啟操作。Readiness 則是透過監控檢測容器是否正常提供服務來決定是否加入到 Service 的轉發列表接收請求流量。Readiness 在升級過程可以發揮重要的作用,防止升級時異常的新版本 Pod 替換舊版本 Pod 導致整個應用將無法對外提供服務的情況。
每個服務必須提供可以正常訪問的介面,在 deployment.yml 檔案配置好相應的監控檢測策略。
...
spec:
...
template:
...
spec:
...
containers:
- name: fpm
livenessProbe:
httpGet:
path: /__PROJECT_NAME__
port: 80
initialDelaySeconds: 3
periodSeconds: 5
readinessProbe:
httpGet:
path: /__PROJECT_NAME__
port: 80
initialDelaySeconds: 3
periodSeconds: 5
...
...
升級策略配置
升級策略我們選擇 RollingUpdate 的方式,即在升級過程中滾動式地逐步新建新版本的 Pod,待新建 Pod 正常啟動後逐步 kill 掉老版本的 Pod,最終全部新版本的 Pod 替換為舊版本的 Pod。
我們還可以設定 maxSurge 和 maxUnavailable 的值分別控制升級過程中最多可以比原先設定多出的 Pod 比例以及升級過程中最多有多少比例 Pod 處於無法提供服務的狀態。
...
spec:
...
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
...
日誌配置
採用 log-tail 對容器日誌進行採集,所有服務的日誌都上報到阿里雲日誌服務的一個 log-store中。在 deployment.yml 檔案裡配置如下:
...
spec:
...
template:
...
spec:
...
containers:
- name: fpm
env:
- name: aliyun_logs_vpgame
value: stdout
- name: aliyun_logs_vpgame_tags
value: topic=__APP_NAME__
...
...
透過設定環境變數的方式來指定上傳的 Logstore 和對應的 tag,其中 name 表示 Logstore 的名稱。透過 topic 欄位區分不同服務的日誌。
監控配置
透過在 Deployment 中增加 annotations 的方式,令 Prometheus 可以獲取每個 Pod 的業務監控資料。配置示例如下:
...
spec:
...
template:
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "80"
prometheus.io/path: /{{ project_name }}/metrics
...
其中 http:// prometheus.io/scrape: "true" 表示可以被 Prometheus 獲取, http:// prometheus.io/port 表示監控資料的埠, http:// prometheus.io/path 表示獲取監控資料的路徑。
service.yml 配置
service.yml 檔案主要對 Service 進行了描述。
apiVersion: v1
kind: Service
metadata:
annotations:
service.beta.kubernetes.io/alicloud-loadbalancer-address-type: intranet
labels:
app: __APP_NAME__
name: __APP_NAME__
namespace: __NAMESPACE__
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: __APP_NAME__
type: LoadBalancer
對 Service 的定義相比於 Deoloyment 要簡單的多,透過定義 spec.ports 的相關引數可以指定 Service 的對外暴露的埠已經轉發到後端 Pod 的埠。spec.selector 則是指定了需要轉發的 Pod 的 label。
另外,我們這邊是透過負載均衡器型別對外提供服務,這是透過定義 spec.type 為 LoadBalancer 實現的。透過增加 metadata.annotations 為 http:// service.beta.kubernetes.io /alicloud-loadbalancer-address-type: intranet 可以在對該 Service 進行建立的同時建立一個阿里雲內網 SLB 作為對該 Service 請求流量的入口。
如上圖所示,EXTERNAL-IP 即為 SLB 的 IP。
總結
在以上工作的基礎上,我們對各個服務劃分為幾類(目前基本上按照語言進行劃分),然後為每一類中的服務透過 .gitlab-ci.yml 制定一套統一的 CI/CD 流程,與此相同的,同一類中的服務共用一個 Deployment 和 Service 模板。這樣我們在進行服務遷移到 Kubernetes 環境時可以實現快速高效地遷移。
當然,這只是遷移實踐路上邁出的第一步,在 Kubernetes 中的服務的穩定性、效能、自動伸縮等方面還需要更深入地探索和研究。
本文為雲棲社群原創內容,未經允許不得轉載。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69949601/viewspace-2662115/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- VPGAME的Kubernetes遷移實踐GAM
- xtts遷移實踐TTS
- kafka資料遷移實踐Kafka
- Swift Static Libraries遷移實踐Swift
- Datapump資料遷移的實踐總結
- 小米Kylin平滑遷移HBase實踐
- Redis叢集slot遷移改造實踐Redis
- PgSQL·最佳實踐·雲上的資料遷移SQL
- cassandra百億級資料庫遷移實踐資料庫
- 360 數科實踐:JanusGraph 到 NebulaGraph 遷移
- 大型系統儲存層遷移實踐
- 達達埋點遷移京東子午線實踐
- Jenkins搭建與資料遷移實踐Jenkins
- 線上資料遷移,數字化時代的必修課 —— 京東雲資料遷移實踐
- 內容遷移或域名切換的期望與實踐
- Hadoop資料遷移MaxCompute最佳實踐Hadoop
- 資料庫平滑遷移方案與實踐分享資料庫
- 攜程MySQL遷移OceanBase最佳實踐|分享MySql
- .net core遷移實踐:專案檔案csproj的轉換
- 阿里雲NAS檔案遷移專案實踐阿里
- 實踐案例:平安健康的 Dubbo3 遷移歷程總結
- 全量、增量資料在HBase遷移的多種技巧實踐
- 高途資料平臺遷移與成本治理實踐
- 一次基於AST的大規模程式碼遷移實踐AST
- Kubernetes 跨 StorageClass 遷移 Persistent Volumes 完全指南
- 從 Oracle 到 TiDB,全鏈路資料遷移平臺核心能力和杭州銀行遷移實踐OracleTiDB
- 兩類常見場景下的雲原生閘道器遷移實踐
- 快速雲原生化,從資料中心到雲原生的遷移最佳實踐
- 華為雲的Kubernetes實踐之路
- Oracle行遷移實驗Oracle
- GaussDB技術解讀系列:資料庫遷移創新實踐資料庫
- Kubernetes監控實踐
- Serverless Kubernetes 落地實踐Server
- Kubernetes Deployment 最佳實踐
- 如何在零停機的情況下遷移 Kubernetes 叢集
- Mysql百萬級資料遷移,怎麼遷移?實戰過沒?MySql
- Jenkins 在 Kubernetes 上的實踐Jenkins
- React專案實踐(二)一個登入頁面的狀態遷移React