溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

VPGAME 的 Kubernetes 遷移實(shí)踐

發(fā)布時(shí)間:2020-06-07 07:27:53 來源:網(wǎng)絡(luò) 閱讀:159 作者:阿里系統(tǒng)軟件技術(shù) 欄目:云計(jì)算

作者 | 伍沖斌? VPGAME 運(yùn)維開發(fā)工程師

導(dǎo)讀:VPGAME 是集賽事運(yùn)營(yíng)、媒體資訊、大數(shù)據(jù)分析、玩家社群、周邊等為一體的綜合電競(jìng)服務(wù)平臺(tái)??偛课挥谥袊?guó)杭州,在上海和美國(guó)西雅圖分別設(shè)立了電競(jìng)大數(shù)據(jù)研發(fā)中心和 AI 研發(fā)中心。本文將講述 VPGAME 將服務(wù)器遷移至 Kubernetes 的過程。

背景

隨著容器技術(shù)的日趨成熟,公司近期計(jì)劃將服務(wù)遷移至容器環(huán)境,通過 Kubernetes 對(duì)容器進(jìn)行調(diào)度、編排和管理。并借此機(jī)會(huì),對(duì)服務(wù)進(jìn)行標(biāo)準(zhǔn)化,優(yōu)化整個(gè) CI/CD 的流程,提高服務(wù)部署的效率。

CI/CD 工具的選擇

CI/CD 工具上,我們選擇了 GitLab-CI。GitLab-CI 就是一套配合 GitLab 使用的持續(xù)集成系統(tǒng),以完成代碼提交之后的安裝依賴、編譯、單元測(cè)試、lint、鏡像構(gòu)建以及發(fā)布等工作。

GitLab-CI 完美地和 GitLab 進(jìn)行集成,在使用的時(shí)候只需要安裝配置 gitlab-runner 即可。GitLab-Runner 在向 GitLab 完成注冊(cè)后可以提供進(jìn)行 CI/CD 操作的環(huán)境,負(fù)責(zé)從 GitLab 中拉取代碼,根據(jù)代碼倉庫中配置的 gitlab-ci.yml ,執(zhí)行相應(yīng)的命令進(jìn)行 CI/CD 工作。

相比于 Jenkins,GitLab-CI 配置簡(jiǎn)單,只需在工程中配置 gitlab-ci.yml 文件完成 CI/CD 流程的編寫,不需要像在 Jenkins 里一樣配置 webhook 回調(diào)地址,也不需要 Jenkins 新建這個(gè)項(xiàng)目的編譯配置。并且個(gè)人認(rèn)為 GitLab 的 CI/CD 過程顯示比 Jenkins 更加美觀。當(dāng)然 Jenkins 依靠它豐富的插件,可以配置很多 GitLab-CI 不存在的功能。按照現(xiàn)在我們的需求, GitLab-CI 簡(jiǎn)單易用,在功能也滿足我們的需求。

服務(wù)運(yùn)行環(huán)境

容器環(huán)境優(yōu)點(diǎn)

傳統(tǒng)的服務(wù)部署方式是在操作系統(tǒng)中安裝好相應(yīng)的應(yīng)用依賴,然后進(jìn)行應(yīng)用服務(wù)的安裝,這種部署方式的缺點(diǎn)是將服務(wù)的程序、配置、依賴庫以及生命周期與宿主機(jī)操作系統(tǒng)緊密地耦合在一起,對(duì)服務(wù)的升級(jí)、擴(kuò)縮容、遷移等操作不是十分便利。

容器的部署方式則是以鏡像為核心,在代碼進(jìn)行編譯構(gòu)建時(shí),將應(yīng)用程序與應(yīng)用程序運(yùn)行所需要的依賴打包成一個(gè)鏡像,在部署階段,通過鏡像創(chuàng)建容器實(shí)例完成服務(wù)的部署和運(yùn)行。從而實(shí)現(xiàn)以應(yīng)用為中心的管理,容器的隔離性實(shí)現(xiàn)了資源的隔離,由于容器不需要依賴宿主機(jī)的操作系統(tǒng)環(huán)境,所以可以很好地保證開發(fā)、測(cè)試和生產(chǎn)環(huán)境的一致性。此外,由于構(gòu)建好的鏡像是不可變的,并且可以通過 tag 進(jìn)行版本控制,所以可以提供可靠、頻繁的容器鏡像構(gòu)建和部署,亦可方便及快速進(jìn)行回滾操作。

Kubernetes 平臺(tái)功能

Kubernetes(簡(jiǎn)稱 k8s),作為一個(gè)容器調(diào)度、編排和管理平臺(tái),可以在物理或虛擬機(jī)集群上調(diào)度和運(yùn)行應(yīng)用程序容器,提供了一個(gè)以容器為核心的基礎(chǔ)架構(gòu)。通過 Kubernetes,對(duì)容器進(jìn)行編排和管理,可以:

  • 快速、可預(yù)測(cè)地部署服務(wù)
  • 擁有即時(shí)擴(kuò)展服務(wù)的能力
  • 滾動(dòng)升級(jí),完成新功能發(fā)布
  • 優(yōu)化硬件資源,降低成本

阿里云容器服務(wù)優(yōu)勢(shì)

我們?cè)诜?wù)遷移中選用了阿里云的容器服務(wù),它基于原生 Kubernetes 進(jìn)行適配和增強(qiáng),簡(jiǎn)化集群的搭建和擴(kuò)容等工作,整合阿里云虛擬化、存儲(chǔ)、網(wǎng)絡(luò)和安全能力,打造云端最佳的 Kubernetes 容器化應(yīng)用運(yùn)行環(huán)境。在便捷性上,可以通過 Web 界面一鍵完成 Kubernetes 集群的創(chuàng)建、升級(jí)以及節(jié)點(diǎn)的擴(kuò)縮容。功能上,在網(wǎng)絡(luò)、存儲(chǔ)、負(fù)載均衡和監(jiān)控方面與阿里云資源集成,在遷移過程中可以最小化減少遷移帶來的影響。

此外,在選擇集群創(chuàng)建時(shí),我們選擇了托管版 Kubernetes,只需創(chuàng)建 Worker 節(jié)點(diǎn),Master 節(jié)點(diǎn)由容器服務(wù)創(chuàng)建并托管。如此一來,我們?cè)?Worker 節(jié)點(diǎn)的規(guī)劃與資源隔離上還是具備自主性和靈活性的同時(shí)不需要運(yùn)維管理 Kubernetes 集群 Master 節(jié)點(diǎn),可以將更多的精力關(guān)注在應(yīng)用服務(wù)上。

GitLab Runner 部署

GitLab CI 工作流程

VPGAME 的 Kubernetes 遷移實(shí)踐cdn.com/c80d3b069a007de5861693ed5a8a7e70f39fffc4.png">

GitLab CI 基本概念

在介紹 GitLab CI 之前,首先簡(jiǎn)單介紹一下 GitLab CI 里的一些基本概念,具體如下:

  • Pipeline:Gitlab CI 里的流水線,每一次代碼的提交觸發(fā) GitLab CI 都會(huì)產(chǎn)生一個(gè) Pipeline;
  • Stage:每個(gè) Pipeline 由多個(gè) Stage 組成,并且每個(gè) Stage 是有先后順序的;
  • Job:GitLab CI 里的最小任務(wù)單元,負(fù)責(zé)完成具有一件事情,例如編譯、測(cè)試、構(gòu)建鏡像等。每個(gè) Job 都需要指定 Stage ,所以 Job 的執(zhí)行順序可以通過制定不同的 Stage 來實(shí)現(xiàn);
  • GitLab Runner:是具體執(zhí)行 Job 的環(huán)境,每個(gè) Runner 在同一時(shí)間只能執(zhí)行一個(gè) Job;
  • Executor:每個(gè) Runner 在向 GitLab 注冊(cè)的時(shí)候需要指定 Executor,來決定通過何種類型的執(zhí)行器來完成 Job。

GitLab CI 的工作流程

當(dāng)有代碼 push 到 GitLab 時(shí),就會(huì)觸發(fā)一個(gè) Pipeline。然后進(jìn)行編譯,測(cè)試和鏡像構(gòu)建等操作等操作,其中每一步操作都為一個(gè) Job。在 CD 階段,會(huì)將 CI 階段構(gòu)建出來的結(jié)果根據(jù)情況部署到測(cè)試環(huán)境或生產(chǎn)環(huán)境。

GitLab Runner 介紹

Gitlab Runner 分類

GitLab 中有三種類型的 Runner ,分別為:

  • shared:所有項(xiàng)目使用
  • group:group下項(xiàng)目使用
  • specific:指定項(xiàng)目使用

我們可以根據(jù)需要向 GitLab 注冊(cè)不同類型的 Runner,注冊(cè)的方式是相同的。

Gitlab Runner 工作過程

Runner 首先會(huì)向 GitLab 發(fā)起注冊(cè)請(qǐng)求,請(qǐng)求內(nèi)容中包含 token、tag 等信息,注冊(cè)成功后 GitLab 會(huì)向 Runner 返回一個(gè) token,后續(xù)的請(qǐng)求,Runner 都會(huì)攜帶這個(gè)請(qǐng)求。

注冊(cè)成功后,Runner 就會(huì)不停的向 GitLab 請(qǐng)求 Job,時(shí)間間隔是 3s。若沒有請(qǐng)求到 Job,GitLab 返回 204 No Content。如果請(qǐng)求到 Job,GitLab 會(huì)把 Job 信息返回來,Runner 在接收到 Job 之后,會(huì)向 GitLab 發(fā)送一個(gè)確認(rèn)請(qǐng)求,同時(shí)更新任務(wù)的狀態(tài)。之后,Runner 開始 Job 的執(zhí)行, 并且會(huì)定時(shí)地將中間數(shù)據(jù),以 Patch 請(qǐng)求的方式發(fā)送給 GitLab。

VPGAME 的 Kubernetes 遷移實(shí)踐

GitLab Runner 的 Executor

Runner 在實(shí)際執(zhí)行 Job 時(shí),是通過調(diào)用 Executor 來完成的。Runner 在注冊(cè)時(shí)提供了 SSH、Shell、Docker、docker-ssh、VirtualBox、Kubernetes 等不同類型的 Executor 來滿足不同的場(chǎng)景和需求。

其中我們常用的有 Shell 和 Docker 等 Executor,Shell 類型主要是利用 Runner 所在主機(jī)的環(huán)境進(jìn)行 Job的執(zhí)行。而 Docker 類型的 Executor 在每個(gè) Job 開始時(shí),拉取鏡像生產(chǎn)一個(gè)容器,在容器里完成 Job,在 Job 完成后,對(duì)應(yīng)的容器就會(huì)被銷毀。由于 Docker 隔離性強(qiáng)、輕量且回收,我們?cè)谑褂脮r(shí)選用 Docker 類型的 Executor 去執(zhí)行 Job,我們只要提前做好 Job 所需環(huán)境的 Docker 鏡像,在每個(gè) Job 定義好 image 即可使用對(duì)應(yīng)的環(huán)境,操作便捷。

GitLab Runner 安裝與配置

Docker 安裝

由于我們需要使用 Docker 類型的 Executor,所以需要在運(yùn)行 Runnner 的服務(wù)器上先安裝 Docker,具體步驟如下(CentOS 環(huán)境):

安裝需要的軟件包,yum-util 提供 yum-config-manager 功能,另外兩個(gè)是 DeviceMapper 驅(qū)動(dòng)依賴:

yum install -y yum-utils device-mapper-persistent-data lvm2

設(shè)置 yum 源:

yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

安裝 Docker:

yum install docker-ce -y

啟動(dòng)并加入開機(jī)啟動(dòng):

systemctl start docker
systemctl enable docker

Gitlab runner 安裝與啟動(dòng)

執(zhí)行下面的命令進(jìn)行 GitLab Runner 的安裝和啟動(dòng):

curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash
sudo yum install gitlab-runner -y

gitlab-runner start

GitLab Runner 注冊(cè)與配置更新

啟動(dòng) GitLab Runner 后還需要向 GitLab 進(jìn)行注冊(cè),在注冊(cè)前需要從 GitLab 里查詢 token。不同類型的 Runner 對(duì)應(yīng)的 token 獲取的路徑不同。shared Runner 需要 admin 權(quán)限的賬號(hào),按如下方式可以獲取對(duì)應(yīng)的 token。

VPGAME 的 Kubernetes 遷移實(shí)踐

其他兩種類型在對(duì)應(yīng)的頁面下( group 或 project 主頁)的 setting—>CI/CD—>Runner 可以獲取到 token。<br />Runner 的注冊(cè)方式分為交互式和非交互式兩種。其中交互式注冊(cè)方式,在輸入 gitlab-runner register 命令后,按照提示輸入注冊(cè)所需要的信息,包括 gitlab url、token 和 Runner 名字等。這邊個(gè)人比較推薦非交互式命令,可以事先準(zhǔn)備好命令,完成一鍵注冊(cè),并且非交互式注冊(cè)方式提供了更多的注冊(cè)選項(xiàng),可以滿足更多樣化的需求。

按如下示例即可完成一個(gè) Runner 的注冊(cè):

gitlab-runner register  --non-interactive  \
--url "http://git.xxxx.cn"  \
--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 執(zhí)行 Job 時(shí) Dokcer 鏡像下載策略。--docker-volumes 指定容器與宿主機(jī)(即 Runner 運(yùn)行的服務(wù)器)的文件掛載映射關(guān)系。上面掛載的文件主要是用于 Runner 在執(zhí)行 Job 時(shí),運(yùn)用的一些 key,包括訪問 GitLab、Docker Harbor 和 Kubernetes 集群的 key。當(dāng)然,如果還有其他文件需要共享給容器,可以通過 --docker-volumes 去指定。

/etc/docker/daemon.json 文件主要為了可以以 http 方式訪問 docker horbor 所做的設(shè)置:

{ "insecure-registries" : ["http://docker.vpgame.cn"] }

完成注冊(cè)后,重啟 Runner 即可:

gitlab-runner restart

部署完成后,可以在 GitLab 的 Web 管理界面查看到不同 Runner 的信息。

VPGAME 的 Kubernetes 遷移實(shí)踐

此外,如果一臺(tái)服務(wù)需要注冊(cè)多個(gè) Runner ,可以修改 /etc/gitlab-runner/config.toml 中的 concurrent 值增加 Runner 的并發(fā)數(shù),修改完之后同樣需要重啟 Runner。

Docker 基礎(chǔ)鏡像制作

為了滿足不同服務(wù)對(duì)運(yùn)行環(huán)境的多樣化需求,我們需要為不同語言的服務(wù)提前準(zhǔn)備不同的基礎(chǔ)鏡像用于構(gòu)建鏡像階段使用。此外,CI/CD 所需要的工具鏡像也需要制作,作為 Runner 執(zhí)行 Job 時(shí)生成容器所需要的 Docker 鏡像。<br />所有的鏡像都以編寫 Dockerfile 的形式通過 GitLab 進(jìn)行管理,并且我們編寫了 .gitlab-ci.yml 文件,使得每次有 Dockerfile 新增或者修改就會(huì)觸發(fā) Pipeline 進(jìn)行鏡像的構(gòu)建并上傳到 Harbor 上。這種管理方式有以下優(yōu)點(diǎn):

  • 按照一定規(guī)則自動(dòng)構(gòu)建鏡像,可以快速便捷地新建和更新鏡像
  • 根據(jù)規(guī)則可以找到鏡像對(duì)應(yīng)的 Dockerfile,明確鏡像的具體組成
  • 團(tuán)隊(duì)成員可以通過提交 Merge Request 自由地構(gòu)建自己需要的鏡像

鏡像分類

VPGAME 的 Kubernetes 遷移實(shí)踐

  • 運(yùn)行時(shí)基礎(chǔ)鏡像:提供各個(gè)語言運(yùn)行時(shí)必須的工具和相應(yīng)的 package。
  • CI 鏡像:基于運(yùn)行時(shí)基礎(chǔ)鏡像,添加單元測(cè)試、lint、靜態(tài)分析等功能,用在 CI/CD 流程中的 test 環(huán)節(jié)。
  • 打包上線鏡像:用在 CI/CD 流程中的 build 和 deploy 環(huán)節(jié)。

Dockerfile 目錄結(jié)構(gòu)

每個(gè)文件夾都有 Dockerfile 來描述鏡像的基本情況,其中包含了 Java、PHP、Node 和 Go 等不同語言的運(yùn)行時(shí)基礎(chǔ)鏡像和 CI 鏡像,還有 docker-kubectl 這類工具鏡像的 Dockerfile。

VPGAME 的 Kubernetes 遷移實(shí)踐

以 PHP 鏡像為例:

php/
├── 1.0
│   ├── Dockerfile
│   ├── ci-1.0
│   │   └── Dockerfile
│   ├── php.ini
│   ├── read-zk-config
│   ├── start_service.sh
│   └── www.conf
└── nginx
    ├── Dockerfile
    ├── api.vpgame.com.conf
    └── nginx.conf

該目錄下有一個(gè)名為 1.0 的文件夾,里面有一個(gè) Dockerfile 用來構(gòu)建 php fpm 運(yùn)行時(shí)基礎(chǔ)進(jìn)行鏡像。主要是在 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 www.conf /usr/local/etc/php-fpm.d/www.conf
WORKDIR /work
CMD ["start_service.sh"]

在 1.0/ci-1.0 還有一個(gè) Dockerfile,是用來構(gòu)建 PHP 在進(jìn)行單元測(cè)試和 lint 操作時(shí)所使用的 CI 鏡像。可以看到它基于上面的基礎(chǔ)運(yùn)行時(shí)鏡像增加其他工具來進(jìn)行構(gòu)建的。

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 https://vp-infra.oss-cn-beijing.aliyuncs.com/gitlab-ci/software/download/1.6.5/composer.phar -O /bin/composer && \
    chmod +x /bin/composer && \
    composer config -g -q repo.packagist composer https://packagist.laravel-china.org
RUN composer global require -q phpunit/phpunit:~5.0 squizlabs/php_codesniffer:~3.0

WORKDIR /

CMD ["/bin/bash"]

另外 Nginx 目錄下同樣有 Dockerfile,來定制化我們 PHP 項(xiàng)目所需要的 Nginx 鏡像。

在 GitLab 里第一次增加新的 Dockerfile 或者更改 Dockerfile 時(shí),會(huì)觸動(dòng) Pipeline 自動(dòng)進(jìn)行鏡像的構(gòu)建并上傳的我們私有的 Docker Harbor 上。

VPGAME 的 Kubernetes 遷移實(shí)踐

鏡像自動(dòng)構(gòu)建基本原理

由于各個(gè)鏡像通過 Dockerfile 進(jìn)行管理, Master 分支有新的合并,可以通過 git diff 命令找出合并前后新增或更新的 Dockerfile,然后根據(jù)這些 Dockerfile 依據(jù)一定的命名規(guī)則構(gòu)建鏡像,并上傳到 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)鏡像地址:http://mirrors.aliyun.com;
  • 一些海外軟件下載會(huì)比較慢,可以先下載下來上傳至阿里云 OSS 后下載。Dockerfile 使用阿里云 OSS 作為下載源,減少構(gòu)建鏡像時(shí)間。

基于 .gitlab-ci.yml 的 CI/CD 流程

在完成 GitLab Runner 以及 Docker 基礎(chǔ)鏡像的制作之后,我們便可以進(jìn)行 CI/CD 流程來完成代碼更新之后的單元測(cè)試、lint、編譯、鏡像打包以及部署等工作。通過 GitLab CI 進(jìn)行 CI/CD 的操作只需要在代碼倉庫里編輯和維護(hù)一個(gè) .gitlab-ci.yml 文件,每當(dāng)代碼有更新,GitLab CI 會(huì)讀取 .gitlab-ci.yml 里的內(nèi)容,生成一條 Pipeline 進(jìn)行 CI/CD 的操作。

.gitlab-ci.yml 的語法比較簡(jiǎn)單,基于 yaml 語法進(jìn)行 Job 的描述。我們把 CI/CD 流程中所需要完成的任務(wù)拆分成文件里的 Job,只要對(duì)每個(gè) Job 完成清晰的定義,便可形成一套合適高效并具有普適性的 CI/CD 流程。

定義 stages

stages 是一個(gè)非常重要的概念, 在 .gitlab-ci.yml 中進(jìn)行全局定義, 在定義 Job 時(shí)指定其中的值來表明 Job 所處的 stage。而在 stages 里元素的順序定義了 Job 的執(zhí)行順序:所有在相同 stage 的 Job 會(huì)并行執(zhí)行,只有當(dāng)前 stage 的所有成功完成后,后面 stage 的 Job 才會(huì)去執(zhí)行。
例如,定義如下 stages:

stages:
  - build
  - test
  - deploy
  1. 首先,所有 build 里的 Job 會(huì)并行執(zhí)行;
  2. 當(dāng) build 里所有 Job 執(zhí)行成功, test 里所有 Job 會(huì)并行執(zhí)行;
  3. 如果 test 里所有 Job 執(zhí)行成功, deploy 里所有 Job 會(huì)并行執(zhí)行;
  4. 如果 deploy 里所有 Job 執(zhí)行成功, 當(dāng)前 Pipeline 會(huì)被標(biāo)記為 passed;
  5. 當(dāng)某個(gè) stage 的 Job 執(zhí)行失敗, Pipeline 會(huì)標(biāo)記為為 failed,其后續(xù)stage 的 Job 都不會(huì)被執(zhí)行。

Job 的描述

Job 是 .gitlab-ci.yml 文件中最重要的組成部分,所有的 CI/CD 流程中所執(zhí)行的任務(wù)均可以需要通過定義 Job 來實(shí)現(xiàn)。具體來說,我們可以通過關(guān)鍵字來對(duì)每一個(gè) Job 進(jìn)行描述。由于 Job 中的關(guān)鍵字眾多,并且用法比較豐富,這邊針對(duì)我們自己實(shí)戰(zhàn)中的一個(gè) Job 來進(jìn)行說明。

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 主要完成了單元測(cè)試的功能,在起始行定義了 Job 的名稱。下面我們來解釋 Job 每個(gè)關(guān)鍵字的具體含義。

  • stage,定義了 Job 所處的 stage,值為定義在全局中 stages 里的值;<br />

  • image,指定了 Runner 運(yùn)行所需要的鏡像,這個(gè)鏡像是我們之前制作的基本鏡像。通過該鏡像運(yùn)行的 Docker 即是 Job 運(yùn)行的環(huán)境;

  • services,Runner 所運(yùn)行的 Docker 所需要的連接依賴,在這邊分別定義了 MySQL 和 Redis,在 Job 運(yùn)行時(shí)會(huì)去連接這兩個(gè)鏡像生成的 Docker;

  • script,Job 運(yùn)行的具體的命令 ,通過 Shell 來描述。此 Job 中的 script 主要完成了代碼的編譯和單元測(cè)試;

  • artifacts,主要是將此 Job 中完成的結(jié)果打包保存下來,可以通過 when 指定何時(shí)保存,path 定義了保存的文件路徑, expire_in 指定了結(jié)果保存的有效期。與之對(duì)應(yīng)的是 dependencies 參數(shù),如果其他 Job 需要此 Job 的 artifacts ,只需要在 Job 按照如下定義即可;
dependencies:
  - unittest
  • only 關(guān)鍵字指定了 Job 觸發(fā)的時(shí)機(jī),該例子中說明只有分支合并或者打 tag 的情況下,該 Job 才會(huì)被觸發(fā);

  • 與 only 相對(duì)還有 except 關(guān)鍵字來排除觸發(fā) Job 某些情況。此外 only 還支持正則表達(dá)式,比如;
job:
  only:
    - /^issue-.*$/
  except:
    - branches

這個(gè)例子中,只有以 issue- 開頭 tag 標(biāo)記才會(huì)觸發(fā) Job。如果不加 except 參數(shù),以 issue- 開頭的分支或者 tag 標(biāo)記會(huì)觸發(fā) Job。

  • tags,tags關(guān)鍵字主要是用來指定運(yùn)行的 Runner 類型。在我們實(shí)際運(yùn)用中,部署測(cè)試環(huán)境和生產(chǎn)環(huán)境所采用的 Runner 是不一樣的,它們是通過不同的 tag 去標(biāo)識(shí)區(qū)分。

VPGAME 的 Kubernetes 遷移實(shí)踐

所以,我們?cè)?Job 定義中,通過 tags 指定 Runner 的值,來指定所需要的 Runner。

我們可以看到 Job 的定義非常的清晰和靈活,關(guān)于 Job 的使用遠(yuǎn)不止這些功能,更詳細(xì)的用法可以參考 GitLab CI/CD 官方文檔。

CI/CD 流程編排

在清楚了如何描述一個(gè) Job 之后,我們通過定義一個(gè)個(gè) Job,并進(jìn)行編排形成 Pipelines。因?yàn)槲覀兛梢悦枋鲈O(shè)定 Job 的觸發(fā)條件,所以通過不同的條件可以觸發(fā)形成不一樣的 Pipelines。<br />在 PHP 項(xiàng)目 Kubernetes 上線過程中,我們規(guī)定了合并 Master 分支會(huì)進(jìn)行 lint、unitest、build-test 以及 deploy-test 四個(gè) Job。

VPGAME 的 Kubernetes 遷移實(shí)踐

在測(cè)試環(huán)境驗(yàn)證通過之后,我們?cè)偻ㄟ^打 tag 進(jìn)行正式環(huán)境的上線。此處的 Pipelines 包含了 unittest、build-pro 和 deploy-pro 三個(gè) Job。

VPGAME 的 Kubernetes 遷移實(shí)踐

在 .gitlab-ci.yml 文件中,test 階段主要完成 lint 和 unitest 兩個(gè) Job,不同的語言在進(jìn)行 Job 定義時(shí)會(huì)各有不同。我們重點(diǎn)來看一下 build 和 deploy 兩個(gè) 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 階段中測(cè)試環(huán)境和生產(chǎn)環(huán)境進(jìn)行鏡像打包時(shí)基本操作時(shí)是相同的,都是根據(jù) Dockerfile 進(jìn)行鏡像的 build 和鏡像倉庫的上傳。這里用到了一個(gè) extend 參數(shù),可以減少重復(fù)的 Job 描述,使得描述更加地簡(jiǎn)潔清晰。

我們先定義一個(gè) .build-op 的 Job,然后 build-test 和 build-prod 都通過 extend 進(jìn)行繼承,可以通過定義關(guān)鍵字來新增或覆蓋 .build-op 中的配置。比如 build-prod 重新定義了變量( variables)DOCKER_IMAGE_NAME以及觸發(fā)條件(only)更改為了打 tag 。

這邊我們還需要注意到的是在定義 DOCKER_IMAGE_NAME 時(shí),我們引用了 GitLab CI 自身的一些變量,比如 CI_COMMIT_TAG 表示項(xiàng)目的 commit 的 tag 名稱。我們?cè)诙x Job 變量時(shí),可能會(huì)引用到一些 GitLab CI 自身變量,關(guān)于這些變量的說明可以參考 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: http://example.com
  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: http://example.com
  only:
  - tags
  tags:
  - pro-deploy

與 build 階段類似,先先定義一個(gè) .deploy-op 的 Job,然后 deploy-test 和 deploy-prod 都通過 extend 進(jìn)行繼承。

.deploy-op 主要完成了對(duì) Kubernetes Deployment 和 Service 模板文件的一些變量的替換,以及根據(jù)生成的 Deployment 和 Service 文件進(jìn)行 Kubernetes 服務(wù)的部署。

deploy-test 和 deploy-prod 兩個(gè) Job 定義了不同變量(variables)以及觸發(fā)條件(only)。除此之外, deploy-prod 通過 tags 關(guān)鍵字來使用不同的 Runner,將部署的目標(biāo)集群指向給生產(chǎn)環(huán)境的 Kubernetes。

這里還有一個(gè)關(guān)鍵字 environment 需要特別說明,在定義了 environment 之后,我們可以在 GitLab 中查看每次部署的一些信息。除了查看每次部署的一些信息之外,我們還可以很方便地進(jìn)行重新部署和回滾。

VPGAME 的 Kubernetes 遷移實(shí)踐

可以看到,通過對(duì) Job 的關(guān)鍵字進(jìn)行配置,我們可以靈活地編排出我們所需要的 CI/CD 流程,非常好地滿足多樣化的場(chǎng)景。

Deployment 與 Service 配置

在 CI/CD 流程中完成 Docker 鏡像的打包任務(wù)之后需要將服務(wù)所對(duì)應(yīng)的鏡像部署到 Kubernetes 集群中。Kubernetes 提供了多種可以編排調(diào)度的資源對(duì)象。首先,我們簡(jiǎn)單了解一下 Kubernetes 中的一些基本資源。

Kubernetes 基本資源對(duì)象概覽

Pod

Pod 作為無狀態(tài)應(yīng)用的運(yùn)行實(shí)體是其中最常用的一種資源對(duì)象,Kubernetes 中資源調(diào)度最小的基本單元,它包含一個(gè)或多個(gè)緊密聯(lián)系的容器。這些容器共享存儲(chǔ)、網(wǎng)絡(luò)和命名空間,以及如何運(yùn)行的規(guī)范。

在 Kubernetes 中,Pod 是非持久的,會(huì)因?yàn)楣?jié)點(diǎn)故障或者網(wǎng)絡(luò)不通等情況而被銷毀和重建。所以我們?cè)?Kubernetes 中一般不會(huì)直接創(chuàng)建一個(gè)獨(dú)立的 Pod,而是通過多個(gè) Pod 對(duì)外提供服務(wù)。

ReplicaSet

ReplicaSet 是 Kubernetes 中的一種副本控制器,控制由其管理的 Pod,使 Pod 副本的數(shù)量維持在預(yù)設(shè)的個(gè)數(shù)。ReplicaSets 可以獨(dú)立使用,但是在大多數(shù)場(chǎng)景下被 Deployments 作為協(xié)調(diào) Pod 創(chuàng)建,刪除和更新的機(jī)制。

Deployment

Deployment 為 Pod 和 ReplicaSet 提供了一個(gè)聲明式定義方法。通過在 Deployment 中進(jìn)行目標(biāo)狀態(tài)的描述,Deployment controller 會(huì)將 Pod 和 ReplicaSet 的實(shí)際狀態(tài)改變?yōu)樗O(shè)定的目標(biāo)狀態(tài)。Deployment 典型的應(yīng)用場(chǎng)景包括:

  • 定義 Deployment 來創(chuàng)建 Pod 和 ReplicaSet
  • 滾動(dòng)升級(jí)和回滾應(yīng)用
  • 擴(kuò)容和縮容
  • 暫停和繼續(xù) Deployment

Service

在 Kubernetes 中,Pod 會(huì)被隨時(shí)創(chuàng)建或銷毀,每個(gè) Pod 都有自己的 IP,這些 IP 也無法持久存在,所以需要 Service 來提供服務(wù)發(fā)現(xiàn)和負(fù)載均衡能力。

Service 是一個(gè)定義了一組 Pod 的策略的抽象,通過 Label Selector 來確定后端訪問的 Pod,從而為客戶端訪問服務(wù)提供了一個(gè)入口。每個(gè) Service 會(huì)對(duì)應(yīng)一個(gè)集群內(nèi)部的 ClusterIP,集群內(nèi)部可以通過 ClusterIP 訪問一個(gè)服務(wù)。如果需要對(duì)集群外部提供服務(wù),可以通過 NodePort 或 LoadBalancer 方式。

<a name="2G399"></a>

deployment.yml 配置

deployment.yml 文件用來定義 Deployment。首先通過一個(gè)簡(jiǎn)單的 deployment.yml 配置文件熟悉 Deployment 的配置格式。

VPGAME 的 Kubernetes 遷移實(shí)踐

上圖中 deployment.yml 分為 8 個(gè)部分,分別如下:

  1. apiVersion 為當(dāng)前配置格式的版本;
  2. kind 指定了資源類型,這邊當(dāng)然是 Deployment;
  3. metadata 是該資源的元數(shù)據(jù),其中 name 是必需的數(shù)據(jù)項(xiàng),還可以指定 label 給資源加上標(biāo)簽;
  4. spec 部分是該 Deployment 的規(guī)格說明;
  5. spec.replicas 指定了 Pod 的副本數(shù)量;
  6. spec.template 定義 Pod 的基本信息,通過 spec.template.metadata 和 spec.template.spec 指定;
  7. spec.template.metadata 定義 Pod 的元數(shù)據(jù)。至少需要定義一個(gè) label 用于 Service 識(shí)別轉(zhuǎn)發(fā)的 Pod, label 是通過 key-value 形式指定的;
  8. spec.template.spec 描述 Pod 的規(guī)格,此部分定義 Pod 中每一個(gè)容器的屬性,name 和 image 是必需項(xiàng)。

在實(shí)際應(yīng)用中,還有更多靈活個(gè)性化的配置。我們?cè)?Kubernetes 的部署實(shí)踐中制定了相關(guān)的規(guī)范,在以上基礎(chǔ)結(jié)構(gòu)上進(jìn)行配置,得到滿足我們實(shí)際需求的 deployment.yml 配置文件。

在 Kubernetes 的遷移實(shí)踐中,我們主要在以下方面對(duì) Deployment 的配置進(jìn)行了規(guī)范的約定:

文件模板化

首先我們的 deployment.yml 配置文件是帶有變量的模版文件,如下所示:

apiVersion: apps/v1beta2
kind: Deployment
metadata:
  labels:
    app: __APP_NAME__
    group: __GROUP_NAME__
  name: __APP_NAME__
  namespace: __NAMESPACE__

APPNAME、GROUPNAMENAMESPACE?這種形式的變量都是在 CI/CD 流程中會(huì)被替換成 GitLab 每個(gè) project 所對(duì)應(yīng)的變量,目的是為了多了 project 用相同的 deployment.yml 文件,以便在進(jìn)行 Kubernetes 遷移時(shí)可以快速復(fù)制,提高效率。

服務(wù)名稱

  • Kubernetes 中運(yùn)行的 Service 以及 Deployment 名稱由 GitLab 中的 groupname 和 projectname 組成,即 {{groupname}}-{{projectname}},例:microservice-common;<br />此名稱記為 app_name,作為每個(gè)服務(wù)在 Kubernetes 中的唯一標(biāo)識(shí)。這些變量可以通過 GitLab-CI 的內(nèi)置變量中進(jìn)行獲取,無需對(duì)每個(gè) project 進(jìn)行特殊的配置。

  • Lables 中用于識(shí)別服務(wù)的標(biāo)簽與 Deployment 名稱保持一致,統(tǒng)一設(shè)置為 app:{{app_name}}。

資源分配

節(jié)點(diǎn)配置策略,以項(xiàng)目組作為各項(xiàng)目 Pod 運(yùn)行在哪些 Node 節(jié)點(diǎn)的依據(jù),屬于同一項(xiàng)目組的項(xiàng)目的 Pod 運(yùn)行在同一批 Node 節(jié)點(diǎn)。具體操作方式為給每個(gè) Node 節(jié)點(diǎn)打上形如 group:__GROUP_NAME__ 的標(biāo)簽,在 deployment.yml 文件中做如下設(shè)置進(jìn)行 Pod 的 Node 節(jié)點(diǎn)選擇:

...
  spec:
    ...
    template:
      ...
      spec:
        ...
        affinity:
          nodeAffinity:
            requiredDuringSchedulingIgnoredDuringExecution:
              nodeSelectorTerms:
              - matchExpressions:
                - key: group
                  operator: In
                  values:
                  - __GROUP_NAME__
     ...

資源請(qǐng)求大小,對(duì)于一些重要的線上應(yīng)用,limit 和 request 設(shè)置一致,資源不足時(shí) Kubernetes 會(huì)優(yōu)先保證這些 Pod 正常運(yùn)行。為了提高資源利用率。對(duì)一些非核心,并且資源不長(zhǎng)期占用的應(yīng)用,可以適當(dāng)減少 Pod 的 request,這樣 Pod 在調(diào)度時(shí)可以被分配到資源不是十分充裕的節(jié)點(diǎn),提高使用率。但是當(dāng)節(jié)點(diǎn)的資源不足時(shí),也會(huì)優(yōu)先被驅(qū)逐或被 oom kill。

健康檢查(Liveness/Readiness)配置

Liveness 主要用于探測(cè)容器是否存活,若監(jiān)控檢查失敗會(huì)對(duì)容器進(jìn)行重啟操作。Readiness 則是通過監(jiān)控檢測(cè)容器是否正常提供服務(wù)來決定是否加入到 Service 的轉(zhuǎn)發(fā)列表接收請(qǐng)求流量。Readiness 在升級(jí)過程可以發(fā)揮重要的作用,防止升級(jí)時(shí)異常的新版本 Pod 替換舊版本 Pod 導(dǎo)致整個(gè)應(yīng)用將無法對(duì)外提供服務(wù)的情況。

每個(gè)服務(wù)必須提供可以正常訪問的接口,在 deployment.yml 文件配置好相應(yīng)的監(jiān)控檢測(cè)策略。

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

升級(jí)策略配置

升級(jí)策略我們選擇 RollingUpdate 的方式,即在升級(jí)過程中滾動(dòng)式地逐步新建新版本的 Pod,待新建 Pod 正常啟動(dòng)后逐步 kill 掉老版本的 Pod,最終全部新版本的 Pod 替換為舊版本的 Pod。

VPGAME 的 Kubernetes 遷移實(shí)踐

我們還可以設(shè)置 maxSurge 和 maxUnavailable 的值分別控制升級(jí)過程中最多可以比原先設(shè)置多出的 Pod 比例以及升級(jí)過程中最多有多少比例 Pod 處于無法提供服務(wù)的狀態(tài)。

...
spec:
  ...
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
...

日志配置

采用 log-tail 對(duì)容器日志進(jìn)行采集,所有服務(wù)的日志都上報(bào)到阿里云日志服務(wù)的一個(gè) 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__
        ...
   ...

通過設(shè)置環(huán)境變量的方式來指定上傳的 Logstore 和對(duì)應(yīng)的 tag,其中 name 表示 Logstore 的名稱。通過 topic 字段區(qū)分不同服務(wù)的日志。

監(jiān)控配置

通過在 Deployment 中增加 annotations 的方式,令 Prometheus 可以獲取每個(gè) Pod 的業(yè)務(wù)監(jiān)控?cái)?shù)據(jù)。配置示例如下:

...
spec:
 ...
 template:
    metadata:
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "80"        
        prometheus.io/path: /{{ project_name }}/metrics
  ...

其中 prometheus.io/scrape: "true" 表示可以被 Prometheus 獲取,prometheus.io/port 表示監(jiān)控?cái)?shù)據(jù)的端口,prometheus.io/path 表示獲取監(jiān)控?cái)?shù)據(jù)的路徑。

service.yml 配置

service.yml 文件主要對(duì) Service 進(jìn)行了描述。

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

對(duì) Service 的定義相比于 Deoloyment 要簡(jiǎn)單的多,通過定義 spec.ports 的相關(guān)參數(shù)可以指定 Service 的對(duì)外暴露的端口已經(jīng)轉(zhuǎn)發(fā)到后端 Pod 的端口。spec.selector 則是指定了需要轉(zhuǎn)發(fā)的 Pod 的 label。

另外,我們這邊是通過負(fù)載均衡器類型對(duì)外提供服務(wù),這是通過定義 spec.type 為 LoadBalancer 實(shí)現(xiàn)的。通過增加 metadata.annotations 為 service.beta.kubernetes.io/alicloud-loadbalancer-address-type: intranet 可以在對(duì)該 Service 進(jìn)行創(chuàng)建的同時(shí)創(chuàng)建一個(gè)阿里云內(nèi)網(wǎng) SLB 作為對(duì)該 Service 請(qǐng)求流量的入口。

VPGAME 的 Kubernetes 遷移實(shí)踐

如上圖所示,EXTERNAL-IP 即為 SLB 的 IP。

總結(jié)

在以上工作的基礎(chǔ)上,我們對(duì)各個(gè)服務(wù)劃分為幾類(目前基本上按照語言進(jìn)行劃分),然后為每一類中的服務(wù)通過 .gitlab-ci.yml 制定一套統(tǒng)一的 CI/CD 流程,與此相同的,同一類中的服務(wù)共用一個(gè) Deployment 和 Service 模板。這樣我們?cè)谶M(jìn)行服務(wù)遷移到 Kubernetes 環(huán)境時(shí)可以實(shí)現(xiàn)快速高效地遷移。

當(dāng)然,這只是遷移實(shí)踐路上邁出的第一步,在 Kubernetes 中的服務(wù)的穩(wěn)定性、性能、自動(dòng)伸縮等方面還需要更深入地探索和研究。

“ 阿里巴巴云原生微信公眾號(hào)(ID:Alicloudnative)關(guān)注微服務(wù)、Serverless、容器、Service Mesh等技術(shù)領(lǐng)域、聚焦云原生流行技術(shù)趨勢(shì)、云原生大規(guī)模的落地實(shí)踐,做最懂云原生開發(fā)者的技術(shù)公眾號(hào)。”

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI