30

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

 3 years ago
source link: https://blog.crazyphper.com/2020/04/24/gitlab-runnerhelm%E5%AE%9E%E7%8E%B0php%E7%A8%8B%E5%BA%8F%E5%9C%A8k8s%E7%9A%84%E8%87%AA%E5%8A%A8%E5%8C%96%E6%9E%84%E5%BB%BA%E4%B8%8E%E9%83%A8%E7%BD%B2/?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

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到集群中。

在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时,错误内容如下:

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="[email protected]"

#阿里云镜像
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看看跑的结果。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK