著者|呉チョン・ビンVPGAME運用・保守開発エンジニア
REVIEW:VPGAMEは、集積ゲームプラットフォームとしてイベント運営、情報メディア、ビッグデータ分析、社会的なプレーヤー、ゲーム周辺機器のセットです。杭州、中国に本社を置き、上海、シアトルで大規模なデータセンターやR&D AIの研究開発センターを、遊技設定しました。この記事ではKubernetesを処理するサーバーを移行VPGAMEについて説明します。
背景
技術として、コンテナ、Kubernetesによって容器のコンテナ環境、スケジューリング、組織と管理にサービスを移行するために、同社の最近の計画を成熟します。そして、サービスを標準化するCI / CD全体の業務プロセスを合理化し、サービス展開の効率を向上させるために、この機会を取ります。
CI / CDツールを選択します
CI / CDツールは、我々はGitLab-CIを選びました。GitLab-CIは、ミラーのリリースを構築するために提出、コンパイル、ユニットテスト、糸くずなどをした後、インストールに依存するコードを完了するために、複雑な統合システムgitlab連続使用です。
のみ使用する場合にGitLab-CIとGitLab完全な統合は、コンフィギュレーション・gitlabランナーをインストールする必要があります。GitLab-ランナーは、環境への登録が完了した後gitlabのCI / CD動作を提供することができる、対応するコマンドCI / CD作業を行う、プルgitlabから構成gitlab-ci.ymlコードリポジトリコードを撮影する責任があります。
ジェンキンス、プロジェクト内の簡単な設定ファイルgitlab-ci.yml完全CI / CD書き込み処理としてGitLab-CIの構成に比べジェンキンスのような構成のウェブフックのコールバックアドレスを好きではない、新しいプロジェクトはジェンキンスを必要としませんパッケージが構築されています。そして、個人的に私は、CI / CDプロセスのGitLabがジェンキンスよりも美しい示していると思います。もちろん、ジェンキンスは、あなたは多くのGitLab-CI機能が存在しない構成することができ、その豊富なプラグインに依存しています。さて、GitLab-CIは使いやすい我々のニーズに応じて、機能は私たちのニーズを満たすためにもです。
サービスのランタイム環境
コンテナの環境の利点
伝統的なサービスの展開が良い、対応するアプリケーションに依存するオペレーティング・システムにインストールし、アプリケーションサービスをインストールしている、この展開の欠点は、サービスをプログラムすることで、コンフィギュレーション、および密接ライフサイクルとホストオペレーティングシステムの依存関係互いに結合され、サービス、スケーラブルな容量をアップグレード、移行操作は非常に便利ではありません。
展開コンテナは、コアとしての画像に基づいてコンパイルされたアプリケーションとアプリケーションを実行する必要が作成するときにミラーリングすることにより、コンテナインスタンスを作成するサービスのコード、配備段階の間、配備および操作にミラーにパッケージ依存します。コンテナは、ホスト・オペレーティング・システム環境に依存しないため、アプリケーション中心の管理を有効にする、隔離コンテナは、リソースのアイソレーションを提供し、それがうまく、開発、テスト環境と本番環境の一貫性を確保することができます。構成された画像が不変であり、タグのバージョンによって制御することができる、信頼できると頻繁画像ビルドを提供し、コンテナを展開することが可能であるので、それは容易にかつ迅速にロールバックすることができます。
Kubernetesのプラットフォーム機能
Kubernetes(以下K8S)は、コンテナのスケジューリング、オーケストレーションおよび管理プラットフォームとして、あなたは、物理または仮想マシンのクラスタ内の容器上のアプリケーションをスケジュールして実行することができ、コアとしてコンテナにインフラストラクチャを提供します。Kubernetesによるスケジューリングと管理のための容器の上に、次のことができます。
- 迅速な、予測可能な導入サービス
- 即時の拡張サービスを持っている能力
- ローリング・アップグレード、新機能リリースの完了
- ハードウェアリソースの最適化、コストの削減
アリクラウドコンテナサービスの利点
私たちは、最高のクラウドを構築するために、クラスタのセットアップおよび拡張工事、アリクラウド、仮想化、ストレージ、ネットワーキングおよびセキュリティ機能の統合を簡素化し、ネイティブKubernetesに基づいて、それは適応し、移行サービスコンテナアリクラウドのサービスを使用して強化しますコンテナアプリケーションのランタイム環境をKubernetes。便利で、Kubernetesキークラスタ、アップグレード、およびノードのスケーリング能力を作成するには、Webインターフェイスを介して行うことができます。機能的には、統合されたネットワーク、ストレージ、負荷分散、および監視とアリのクラウドリソースは、移行プロセス中にマイグレーションによる影響を低減するために最小化することができます。
また、クラスタ作成の選択は、私たちがホストされたバージョンKubernetesを選んだのは、単にノードワーカーを作成し、マスターノードが作成され、コンテナサービスによってホストされています。このように、我々はまだ運転・保守管理Kubernetesマスタークラスタノードが、あなたがアプリケーションサービス上でより多くのエネルギーを集中することができます必要はありません同時に自律性と柔軟性を持っている計画やリソース隔離ワーカーノードです。
GitLabランナーの展開
GitLab CIワークフロー
GitLab CIの基本概念
次のようにGitLab CIを導入する前に、まず簡単に、GitLab CIにおける基本的な概念のいくつかを説明します。
- パイプライン:Gitlab CIは、パイプラインでは、各パイプラインを持っていますGitLab CIをトリガーするコードをコミットします。
- ステージ:各パイプラインステージ複数、及び各々が逐次段階です。
- 求人:最小のタスク単位でのGitLab CIは、そのようなミラーリングを構築し、テストをコンパイルするように、一つの電荷を有する完全な。ジョブの実行順序が異なるステージの開発によって達成することができるように、各ジョブには、ステージを指定する必要があります。
- GitLabランナー:ジョブ固有の実行環境、同時に各ランナーは、ジョブを実行することができます。
- エグゼキュータ:エグゼキュータはを通じて仕事を完了するために、アクチュエータの種類を決定するために、GitLabへの登録時に、各ランナーのために指定する必要があります。
GitLab CIワークフロー
GitLabにコードプッシュすると、それは、パイプラインがトリガされます。次に、動作は、コンパイルされたテストし、各ステップがジョブであることを特徴とする請求鏡は、操作と同様の構成します。CDの段階では、ステージは、状況に応じて、テストや本番環境への展開結果のCIを構築することになります。
GitLabランナーの紹介
Gitlabランナーカテゴリ
それぞれGitLabランナー、次の3種類があります。
- 共有:すべての項目は使用します
- グループ:プロジェクトの用途の下でグループ
- 特定:指定されたアイテムを使用します
私たちは、登録が同じ方法で、必要なGitLabにランナーの異なるタイプを登録することができます。
Gitlabランナー作業工程
ランナーは最初、GitLab登録要求にコンテンツトークン、タグ、およびその他の情報に含まれる要求を起動する、GitLabは、登録が成功した後ランナーにトークンを返し、後続の要求、ランナーはこの要求を運ぶでしょう。
登録が成功した後、ランナーは、時間間隔は3秒で、仕事にGitLab要求を維持します。仕事への要求がない場合は、GitLabは204コンテンツなしを返します。仕事への要求した場合、求人情報gitlabは、ジョブを受け取った後、戻ってランナーを返しますが、それはGitLabに確認要求を送信し、タスクのステータスを更新します。GitLabのやり方パッチリクエストで、後、仕事のランナーが開始され、中間データは定期的になります。
GitLabランナー的キュータ
仕事の実際の実装ではランナーは、エグゼキュータを呼び出すことによって行われます。ランナーは、SSH、シェル、ドッカー、ドッキングウィンドウ-SSH、VirtualBoxは、異なるシナリオや要件を満たすために、登録時のKubernetesなどキュータの異なる種類を提供しています。
私たちはしばしば、このような執行、シェルとシェルとドッカーを持っているホストランナーの使用ジョブの実行環境の主要なタイプです。ジョブが完了した後ドッカータイプと各ジョブエグゼキュータの始まりは、ミラーリングされたプル容器の製造、ジョブ内の容器が、完了すると、対応する容器が破壊されます。ジョブを実行するために使用された場合ドッカー分離と強力な、軽量、リサイクルので、我々はエグゼキュータのドッカータイプを使用する限り、私たちは事前にドッカーミラー仕事環境を行うために、必要に応じて、良好な画像の定義は、ジョブに対応する環境のそれぞれで使用することができます、便利な操作。
インストールと設定GitLabランナー
ドッカーインストール
由于我们需要使用 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 https://download.docker.com/linux/centos/docker-ce.repo
安装 Docker:
yum install docker-ce -y
启动并加入开机启动:
systemctl start docker
systemctl enable docker
Gitlab runner 安装与启动
执行下面的命令进行 GitLab Runner 的安装和启动:
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 注册与配置更新
启动 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 "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 执行 Job 时 Dokcer 镜像下载策略。--docker-volumes 指定容器与宿主机(即 Runner 运行的服务器)的文件挂载映射关系。上面挂载的文件主要是用于 Runner 在执行 Job 时,运用的一些 key,包括访问 GitLab、Docker Harbor 和 Kubernetes 集群的 key。当然,如果还有其他文件需要共享给容器,可以通过 --docker-volumes 去指定。
/etc/docker/daemon.json 文件主要为了可以以 http 方式访问 docker horbor 所做的设置:
{ "insecure-registries" : ["http://docker.vpgame.cn"] }
完成注册后,重启 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
│ └── www.conf
└── 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 www.conf /usr/local/etc/php-fpm.d/www.conf
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 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 项目所需要的 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)镜像地址:http://mirrors.aliyun.com;
- 一些海外软件下载会比较慢,可以先下载下来上传至阿里云 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: 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 阶段类似,先先定义一个 .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
...
其中 prometheus.io/scrape: "true" 表示可以被 Prometheus 获取,prometheus.io/port 表示监控数据的端口,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 为 service.beta.kubernetes.io/alicloud-loadbalancer-address-type: intranet 可以在对该 Service 进行创建的同时创建一个阿里云内网 SLB 作为对该 Service 请求流量的入口。
如上图所示,EXTERNAL-IP 即为 SLB 的 IP。
总结
上記の作業に基づいて、我々はサービスの様々な種類の(現在の大部分の言語に応じて分割)は、次いで.gitlab-ci.ymlによるサービスの種類ごとに統一されたCI / CDプロセスを開発分割し、これは同じ、サービスと導入サービスの同じクラスには、テンプレートを共有しています。このように、我々はKubernetes環境への移行、移行時に迅速かつ効率的なサービスを実現することができます。
もちろん、これには、最初の練習の移行パスのステップだけでなく、より安定性の深い探査と研究、パフォーマンス、サービスの自動格納式Kubernetesとあります。
「アリババクラウドネイティブマイクロチャネルパブリック番号(ID:Alicloudnative)フォーカスマイクロサービスで、サーバレス、コンテナ、サービスメッシュ及び他の技術分野、クラウドネイティブで人気の技術動向を中心に、クラウドネイティブの大規模な着陸の練習は、ほとんどがクラウドネイティブ開発を理解してください技術公衆番号。」
ブログ記事複数のプラットフォームからこの記事OpenWriteリリース!