为了让研发团队快速持续迭代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到集群中。
由于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 container
的WARRNING级别错误,常发生在有多个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="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看看跑的结果。
发表回复