Gitlab Runner+Helm实现PHP程序自动化构建与部署最佳实践 - konakona
konakona
Dream Afar.
konakona

Gitlab Runner+Helm实现PHP程序自动化构建与部署最佳实践

为了让研发团队快速持续迭代PHP项目,采用Dockerfile(Nginx+PHP7.2+supervisor)+Helm部署的方式实现CICD。

软件情况说明:

  • Harbor企业级镜像中心:使用docker-compose部署,版本 1.10.0
  • GitLab:使用docker部署,版本 12.5.2
  • Gitlab-Runner:使用docker部署,版本 latest
  • Helm:kubernetes包管理工具,版本 3.0.0-rc2

重点讲一些场景要点和实现过程,Harbor(安装说明)和Gitlab安装方式此处省略。


注意事项

由于PHP的依赖项较多,Kubernetes的DNS(kube-dns / CoreDNS)服务并不稳定,在Pipeline中会引起严重的超时导致构建失败,Gitlab-Runner建议使用Docker部署在服务器上,而非使用Helm部署Gitlab-Runner到集群中。

https://blog.img.crazyphper.com/2020/04/时好时坏-402x600.jpg
在Helm部署的Runner中不断访问阿里云镜像,有40%的失败概率

由于Helm3 不再需要Tiller,因此Gitlab UI 安装Helm Tiller会失败(因为是Helm2的功能),致使Gitlab-Runner没有访问集群的权限,本文采用的是将kubeconfig放在config.toml[[runners]]中映射到环境变量里。


Runner配置

可以将gitlab-ci.yml常用的一些环境变量设置在config.toml[[runners]]下的environment里,不用每次创建项目时都跑去Setting->里设置variables。比如docker login要用到的Harbor镜像地址和账号密码就可以添加后省事了。

[[runners]]
environment = ["DOCKER_TLS_CERTDIR=","HUB_DEV_ADDR=hub.***.com","HUB_DEV_PASSWORD=***","HUB_DEV_USERNAME=***","DOCKER_DRIVER=overlay2","GIT_DEPTH=10","KUBECONFIG_C="***"]

DOCKER_TLS_CERTDIR=空代表不使用TLS;
DOCKER_DRIVER是overlay2与服务器设置要一致;
GIT_DEPTH=10代表浅克隆;
KUBECONFIG_C的值是base64编码后的kubeconfig,使用下边这条命令:

cat ~/.kube/config | base64

再次强调是[[runners]]而非[runners.docker],很多搜索结果都说是放[runners.docker]是错误的。

WARRNING already in use by container

有时你可能会遇到already in use by containerWARRNING级别错误,常发生在有多个stage时,错误内容如下:

https://blog.img.crazyphper.com/2020/04/image-5-6-800x389.png
ERROR: Preparation failed: Error response from daemon: Conflict. The container name "/runner-465639a0-project-44-concurrent-3-build" is already in use by container "109a38a9e6ef215f4518992c652e04572e5977b5bfbef72d038a0cf1bb662946". You have to remove (or rename) that container to be able to reuse that name.

这个问题困扰了一部分Gitlab用户,我在这个issue里发现了大量的讨论,持续近2年的官方团队跟进,定义为P2级Bug(对产品影响比较大,如果发布给用户将会产生麻烦),在3个星期前刚刚修复,需要在[runners.docker]中指定helper_image

[[runners]]
  name = "my runner"
  ...
  [runners.docker]
    helper_image = "gitlab/gitlab-runner-helper:x86_64-1b659122"

文件结构介绍

在PHP程序Git目录下需要准备一些配置文件。

> $ tree  -L 1 -f                                                                                                                                                                    
 
.
├── ./Charts
├── ./Config
├── ./Dockerfile
├── ./src
├── ./.gitlab-ci.yml
└── ./README.md

3 directories, 3 files

/Charts用于存放Helm部署所需配置文件
/Config用于存放Dockerfile创建的容器内的配置文件
/src用于存放需要部署的程序

完整的样例已上传Github

Helm Charts

需要为验收环境和生产环境准备不通的数据库信息、CDN等等,charts的配置方式每个人的习惯和用法都不一样,所以只谈下与环境变量有关的配置好了。

Deployment中,需要配置PHP程序需要的env

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  template:
    spec:
 containers:
      - name: frontend
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        imagePullPolicy: {{ .Values.image.pullPolicy }}
        env:
        - name: APP_DEFAULT_TIMEZONE
          value: "Asia/Shanghai"
        - name: APP_DEBUG
          value: {{ .Values.backend.debug | quote }}
        - name: APPLICATION_ENV
          value: {{ .Values.backend.active }}
        - name: DATABASE_URL
          value: {{ .Values.backend.datasource.url }}
        - name: DATABASE_HOSTNAME
          value: {{ .Values.backend.datasource.host }}
        - name: DATABASE_DATABASE
          value: {{ .Values.backend.datasource.database }}
        - name: DATABASE_USERNAME
          value: {{ .Values.backend.datasource.username }}
        - name: DATABASE_PASSWORD
          value: {{ .Values.backend.datasource.password }}

gitlab-ci.yml

先来说一说CICD配置部分,需求是提供验收和生产环境。


stages:
  - build
  - deploy

services:
- docker:18.09-dind

variables:
  #CI_DEBUG_TRACE: "true"
  GIT_DEPTH: 10
  PROJECT_NAME: oa-domain-net
  REPO_NAME: frontend
  IMAGE_TAG: ${CI_COMMIT_SHA}
  DEVELOP_IMAGE: ${HUB_DEV_ADDR}/${PROJECT_NAME}/${REPO_NAME}:${CI_COMMIT_SHA}
  PRODUCTION_IMAGE: ${HUB_DEV_ADDR}/${PROJECT_NAME}-production/${REPO_NAME}:${CI_COMMIT_TAG}
  

编译(Staging):
  services:
  - docker:18.09-dind
  stage: build
  image: docker:stable
  script:
  - echo "start to build"
  - docker build -t ${DEVELOP_IMAGE} .
  - docker login -u $HUB_DEV_USERNAME -p $HUB_DEV_PASSWORD $HUB_DEV_ADDR
  - docker push ${DEVELOP_IMAGE}
  only:
  - master

部署(Staging):
  stage: deploy
  image: 
    name: alpine/helm:3.0.0-rc.2
    entrypoint: [""]
  script:
  - init_helm
  - helm upgrade --namespace ${PROJECT_NAME}-staging -f Charts/staging_values.yaml --set image.tag=${CI_COMMIT_SHA} --set image.repository=${HUB_DEV_ADDR}/${PROJECT_NAME}/${REPO_NAME} ${REPO_NAME} Charts/
  only:
  - master

编译(Production):
  stage: build
  image: docker:stable
  script:
  - docker build -t ${PRODUCTION_IMAGE} .
  - docker login -u $HUB_DEV_USERNAME -p $HUB_DEV_PASSWORD $HUB_DEV_ADDR
  - docker push ${PRODUCTION_IMAGE}
  only:
  - tags

部署(Production):
  stage: deploy
  image: 
    name: alpine/helm:3.0.0-rc.2
    entrypoint: [""]
  script:
  - init_helm
  - helm upgrade --namespace ${PROJECT_NAME}-production -f Charts/production_values.yaml --set image.tag=${CI_COMMIT_TAG} --set image.repository=${HUB_DEV_ADDR}/${PROJECT_NAME}/${REPO_NAME} ${REPO_NAME} Charts/
  only:
  - tags


.functions: &functions |
    # Functions
    function init_helm() {
        echo $KUBECONFIG_C | base64 -d > ./kubeconfig
        export KUBECONFIG="./kubeconfig"
    }

before_script:
- *functions

在进行部署之前会先使用init_helm方法将Runner容器中含有kubeconfig编码内容的环境变量解码成$KUBECONFIG环境变量来实现集群访问。

Dockerfile

使用nginx+php+supervisord(进程管理)。

这里使用php:7.2-fpm-alpine部署一份ThinkPHP6.0的程序。

# FROM php:7.2-fpm
FROM daocloud.io/php:7.2-fpm-alpine
ENV TZ=Asia/Shanghai
LABEL maintainer="konakona@crazyphper.com"

#阿里云镜像
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories

# 阿里云的内网DNS,如果不需要可以注释掉
RUN echo 'nameserver 100.100.2.136 \n \
nameserver 100.100.2.138' >> /etc/resolv.conf

RUN apk update && apk add --no-cache --virtual .build-deps \
        $PHPIZE_DEPS \
        curl-dev \
        imagemagick-dev \
        libtool \
        libxml2-dev \
        postgresql-dev \
        #sqlite-dev \
	libmcrypt-dev \
        freetype-dev \
        libjpeg-turbo-dev \
        libpng-dev \
    && apk add --no-cache \
        curl \
        git \
        imagemagick \
        mysql-client \
        postgresql-libs \
        nodejs \
        nodejs-npm \
    # 配置npm中国镜像
    && npm config set registry https://registry.npm.taobao.org \
    && pecl install imagick \
    && pecl install mcrypt-1.0.1 \
    && docker-php-ext-enable mcrypt \
    && docker-php-ext-enable imagick \
    && docker-php-ext-install \
        curl \
        mbstring \
        pdo \
        pdo_mysql \
        pdo_pgsql \
        #pdo_sqlite \
        pcntl \
        #tokenizer \
        xml \
        zip \
	&& docker-php-ext-install -j"$(getconf _NPROCESSORS_ONLN)" iconv \
    && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
    && docker-php-ext-install -j"$(getconf _NPROCESSORS_ONLN)" gd \
    && pecl install -o -f redis \
    && rm -rf /tmp/pear \
    && docker-php-ext-enable redis

RUN apk add --no-cache nginx supervisor procps

ENV COMPOSER_ALLOW_SUPERUSER=1
ENV COMPOSER_NO_INTERACTION=1
ENV COMPOSER_HOME=/usr/local/share/composer

RUN mkdir -p /usr/local/share/composer \
	&& curl -o /tmp/composer-setup.php https://getcomposer.org/installer \
	&& php /tmp/composer-setup.php --no-ansi --install-dir=/usr/local/bin --filename=composer --snapshot \
	&& rm -f /tmp/composer-setup.* \
    # 配置composer中国全量镜像
    && composer config -g repo.packagist composer https://packagist.phpcomposer.com

RUN mkdir /run/nginx
RUN rm /etc/nginx/conf.d/*
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
COPY Config/nginx/default.conf /etc/nginx/conf.d/default.conf
# COPY docker/config/nginx/default /etc/nginx/sites-available/default
COPY Config/php/php-fpm.d/docker.conf /usr/local/etc/php-fpm.d/zz-docker.conf
COPY Config/php/php.ini /usr/local/etc/php/php.ini
COPY Config/supervisor/supervisord.conf /etc/supervisor/supervisord.conf
COPY Config/start.sh /usr/local/bin/start


COPY src /var/www
WORKDIR /var/www

RUN chown -R www-data:www-data /var/www/ \
    && chmod +x /usr/local/bin/start 
    
RUN chmod -R 755 public \
 && chmod -R 777 runtime \
 && composer selfupdate --no-plugins

CMD ["/usr/local/bin/start"]

另外把一些常用的镜像也一起放在这里方便大家自取:

#中科大镜像
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories

#163镜像
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.163.com/g' /etc/apk/repositories

# 清华大学镜像,慢死你
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories

/Config/目录下有针对php、nginx、supervisord的各项配置。

nginx/default.conf

server{
    listen 80;
    listen [::]:80;
    index index.php index.html;
    server_name _;
    charset utf-8;
    error_log /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /var/www/public;

    location = /robots.txt {
        log_not_found off;
        access_log off;
    }

    location = /favicon.ico {
        log_not_found off;
        access_log off;
    }

    if (!-e $request_filename) {
       rewrite  ^(.*)$  /index.php?s=/$1  last;
       break;
    }

    location ~ \.php$ {
        try_files $uri = 404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/usr/local/var/run/php-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

php/php.ini

只需要写入部分配置即可,不需要完整的php.ini文件。

由于自动构建多环境的需求,我们不能够再依赖.env来为程序提供所需的环境变量,而是要让PHP的getenv()$_ENV能够读取到系统环境变量,所以需要修改variables_order的设置。同时Helm Chart Deployment部分也需要定义好对应的环境变量。

# php.ini
variables_order = "EGPCS"
extension=pdo_pgsql.so
extension=pdo_mysql.so

php/php-fpm.d/docker.conf

[global]
daemonize=no
pid=run/php-fpm.pid

[www]
listen=/usr/local/var/run/php-fpm.sock
listen.owner=www-data
listen.group=www-data
listen.mode=0660

supervisor/supervisord.conf

[supervisord]
nodaemon=true
pidfile=/var/run/supervisord.pidfile
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

[program:nginx]
autostart=true
autorestart=true
command=nginx -g "daemon off;"
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

[program:php-fpm]
command=php-fpm
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

有了这些准备,我们还需要做一件事,那就是先试一试。

docker build -t name .
docker run -p 8855:80 name 
curl http://localhost:8855

当能够正常访问到程序后,说明Dockerfile和/Config配置正确无误。

接下来就是根据情况调整Helm Charts后上传到Gitlab看看跑的结果。

https://blog.img.crazyphper.com/2020/04/image-5-7-800x72.png

赞赏
# # # # # # #
首页      程序开发      Linux      DevOps      Gitlab Runner+Helm实现PHP程序自动化构建与部署最佳实践

团哥

文章作者

继续玩我的CODE,让别人说去。 低调,就是这么自信。

konakona

Gitlab Runner+Helm实现PHP程序自动化构建与部署最佳实践
为了让研发团队快速持续迭代PHP项目,采用Dockerfile(Nginx+PHP7.2+supervisor)+Helm部署的方式实现CICD。
扫描二维码继续阅读
2020-04-24