基于Docker的前后端分离项目CD/CI

按:宿主机为CentOS7.4,NodeJS环境前端项目,SpringBoot Maven后端项目,Git代码仓库,Jenkins打包部署,Nginx做负载均衡。

安装Docker

CentOS通过yum安装的Docker版本有点低,需要安装Docker-CE:

# 添加安装源
sudo yum-config-manager --add-repo 
 https://download.docker.com/linux/centos/docker-ce.repo
# 安装Docker-CE
sudo yum install docker-ce
# 启动Docker服务
sudo systemctl start docker
# 设置开机启动
sudo systemctl enable docker
# 修改启动代码支持远程访问(Docker中的Jenkins会调用docker命令远程访问宿主机的Docker)
sudo vi /usr/lib/systemd/system/docker.service
在ExecStart的末尾添加如下启动参数
-H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375
# 重新加载修改后的文件
sudo systemctl daemon-reload
# 重启Docker服务
sudo systemctl restart docker

安装包含Docker命令的Jenkins

Jenkins镜像本身不包含Docker命令,需要自行制作一个包含Docker命令的镜像并启动容器。

mkdir ~/jenkins_with_docker
cd ~/jenkins_with_docker
vi Dockerfile
# 开始Dockerfile
FROM jenkins/jenkins:jdk11
USER root
RUN curl -O https://get.docker.com/builds/Linux/x86_64/docker-latest.tgz \
    && tar zxvf docker-latest.tgz \
    && cp docker/docker /usr/local/bin/ \
    && rm -rf docker docker-latest.tgz
ARG DOCKER_GID=999
USER jenkins:${DOCKER_GID}
# 结束Dockerfile
# 构建镜像
sudo docker build -t myjenkins .
# 创建存放Jenkins文件的目录(删除重建容器时不会破坏原有数据)
sudo mkdir /data/jenkins
# 更改数据目录权限
chown -R 1000:1000 /data/jenkins
# 创建并运行容器
docker run -d --name jenkins --restart=always -p 8080:8080 -p 50000:50000 --privileged=true -v /data/jenkins:/var/jenkins_home myjenkins
# 浏览器访问宿主机的8080端口,安装插件,创建用户
# Unlock Jenkins时需要输入密码,进入jenkins容器交互界面,查询密码
sudo docker exec -it jenkins /bin/bash
cat /var/jenkins_home/secrets/initialAdminPassword
# 安装插件前,把插件地址改为国内镜像以提高安装速度
# 打开地址 http://ip-address:8080/manage/pluginManager/advanced
# 最下面的URL更新为以下地址或者其他有效的插件更新地址:https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json
# 安装插件时需要安装Docker plugin, docker-build-step, NodeJS plugin
# 其实docker的插件也可以不装,我们直接执行shell命令操作宿主机的docker
# 配置 Jenkins:在系统设置-全局工具配置中添加NodeJS和Maven。都选择在线安装,如果不同项目用到Node多个版本,可以安装多个。
# 如果使用Docker插件而不是shell操作Docker的话,需要在系统设置中配置docker builder,tcp://xxx.xxx.xxx.xxx:2375

前端项目配置

# 前端项目中需要先准备一个Dockerfile用于构建镜像
# /docker/Dockerfile
FROM nginx:stable-alpine
WORKDIR /usr/local/bin
COPY ./ /usr/share/nginx/html

Jenkins中新建一个自由风格的项目,把git源配置好,在“构建环境”中勾选“ Provide Node & npm bin/ folder to PATH ”,选择项目使用的NodeJS版本。在“构建”中执行Shell命令:

echo $PATH
node -v
npm -v
# 避免被屏蔽国外网站下载失败
npm install chromedriver --chromedriver_cdnurl=http://cdn.npm.taobao.org/dist/chromedriver
npm install
npm run build

Shell方式执行Docker命令

cd ${WORKSPACE}/dist/
cp ../docker/Dockerfile ./
docker -H 宿主机IP build -t front-end-demo .

# 停止指定镜像的容器
docker -H 宿主机IP container ls| awk '$2=="front-end-demo"{system("docker -H 宿主机IP stop " $1)}'
# 删除指定镜像的容器
docker -H 宿主机IP container ls -a| awk '$2=="front-end-demo"{system("docker -H 宿主机IP rm " $1)}'
# 停止指定镜像的容器
docker -H 宿主机IP container ls|grep front-end-demo-container|awk '{system("docker -H 宿主机IP stop " $1)}'
# 删除指定镜像的容器
docker -H 宿主机IP container ls -a|grep front-end-demo-container|awk '{system("docker -H 宿主机IP rm " $1)}'
# 根据需要可以启动多个
docker -H 宿主机IP run -d --name front-end-demo-container-1 -p 9901:80 front-end-demo
docker -H 宿主机IP run -d --name front-end-demo-container-2 -p 9902:80 front-end-demo

# 总是有一个<none>的多余镜像,删掉它
docker -H 宿主机IP image ls|awk '$2=="<none>"{system("docker -H 宿主机IP image rm "$3)}'

配置后端项目

后端项目Dockerfile文件,用于构建镜像:

FROM jdk8

VOLUME /tmp
# 下面这个文件名要根据实际打包的来调整
ADD demo-1-0.0.1-SNAPSHOT.jar /app.jar
RUN touch /app.jar
EXPOSE 80
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

由于没有找到尺寸小的jdk8的官方标准镜像,我们还是自己构建一个,新建一个目录,把下载到的jdk*.tar.gz拷贝到这个目录并新建Dockerfile如下:

FROM frolvlad/alpine-glibc
#FROM centos:7

MAINTAINER sunits
WORKDIR /usr
RUN mkdir  /usr/local/java

ADD jdk-8u211-linux-x64.tar.gz /usr/local/java/

ENV JAVA_HOME /usr/local/java/jdk1.8.0_211
ENV JRE_HOME $JAVA_HOME/jre
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
ENV PATH $JAVA_HOME/bin:$PATH

构建镜像:

sudo docker build -t jdk8 .

这里按SpringBoot项目打jar包的情况来配置。
Jenkins中新建一个Maven项目,如果没有这个选项,就添加Maven插件Maven Integration plugin。Maven的goal设置为 clean package
pom.xml中的build部分增加jar包的输出路径:

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<outputDirectory>src/docker</outputDirectory>
				</configuration>
			</plugin>
		</plugins>
	</build>

Post Steps中添加Shell命令

cd ${WORKSPACE}/src/docker/
docker -H 宿主机IP build -t spring-boot-demo .

# 停止指定镜像的容器
docker -H 宿主机IP container ls| awk '$2=="spring-boot-demo"{system("docker -H 宿主机IP stop " $1)}'
# 删除指定镜像的容器
docker -H 宿主机IP container ls -a| awk '$2=="spring-boot-demo"{system("docker -H 宿主机IP rm " $1)}'
# 停止指定镜像的容器
docker -H 宿主机IP container ls|grep spring-boot-demo-container|awk '{system("docker -H 宿主机IP stop " $1)}'
# 删除指定镜像的容器
docker -H 宿主机IP container ls -a|grep spring-boot-demo-container|awk '{system("docker -H 宿主机IP rm " $1)}'

# 根据需要可以启动多个容器
docker -H 宿主机IP run -d --name spring-boot-demo-container-1 -p 9911:8080 spring-boot-demo

docker -H 宿主机IP run -d --name spring-boot-demo-container-2 -p 9912:8080 spring-boot-demo

# 总是有一个<none>的多余镜像,删掉它
docker -H 宿主机IP image ls|awk '$2=="<none>"{system("docker -H 宿主机IP image rm "$3)}'

Nginx做负载均衡

先做对应的域名解析,后端:be.demo.com, 前端:fe.demo.com,指向nginx服务器,nginx这里不用容器:

yum install nginx
systemctl start nginx
systemctl enable nginx
# 修改/etc/nginx/nginx.conf,在http节内最后添加
include /etc/nginx/vhosts/*.conf;
# 然后就可以每个站点一个conf文件
# 前端文件 /etc/nginx/vhosts/fe.conf
# 开始 fe.conf
upstream fe {
    server 127.0.0.1:9901;  // 启动几个容器就可以配置几个
    server 127.0.0.1:9902;
}
server {
    listen       80;
    server_name fe.demo.com;
    location / {
        proxy_pass http://fe;
    }
}
# 结束 fe.conf

# 后端文件 /etc/nginx/vhosts/be.conf
# 开始 be.conf
upstream be {
    server 127.0.0.1:9911;  // 启动几个容器就可以配置几个
    server 127.0.0.1:9912;
}
server {
    listen       80;
    server_name be.demo.com;
    location / {
        proxy_pass http://be;
    }
}
# 结束 be.conf

# 重新加载配置文件或者重启
systemctl reload/restart nginx

其他常用Docker容器创建

#启动一个Redis容器
sudo docker run --name myredis -d --restart=always -p 6379:6379 --privileged=true -v /data/redis/data:/data redis:alpine
#启动一个MariaDB容器
docker run -d --name mariadb --privileged=true --restart=always -v /data/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 mariadb --lower_case_table_names=1
#启动一个禅道容器
sudo docker run -d --restart=always --name zentao -p 8081:80 -v /data/zentao/www:/app/zentaopms -v /data/zentao/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d zentao:latest
#启动一个gitlab容器
GITLAB_HOME=/data/gitlab
docker run --hostname gitlab --privileged=true -p 8443:443 -p 80:80 -p 2222:22 --name gitlab --restart always -v $GITLAB_HOME/config:/etc/gitlab -v $GITLAB_HOME/logs:/var/log/gitlab -v $GITLAB_HOME/data:/var/opt/gitlab gitlab/gitlab-ce

安装RabbitMQ

docker pull rabbitmq:management-alpine
sudo docker run -d --restart=always --hostname my-rabbit -v /data/rabbitmq/data:/var/lib/rabbitmq --name rabbit -p 15672:15672 -p 4369:4369 -p 5671:5671 -p 5672:5672 -p 25672:25672 -p 61614:61614 -p 61613:61613 rabbitmq:management-alpine

纪念小白

去年初秋全家去京西山里游玩,吃住在一个农家院,其间女儿看到一只白色小波斯猫,特别喜爱,央求我把它带回家养,店主也很大方,说想要的话就送给你们,于是我们把它带了回来。

由于之前没有养过猫,猫砂猫粮都要置备,小家伙慢慢习惯并喜欢上了新环境。后来慢慢成为了我们的家人,儿子还以小白为主题写了英语作文。

刚来的时候我们住10楼,基本没有带他下过楼,今年3约月我们搬家到一楼,有个小院子,这样我们就把小白放开了,基本每天还要回家吃东西喝水,爱躺在门口的沙发上晒太阳,如果有人坐了那个沙发,他也会坚持要躺到那个位置。

也许是在外面跑接触了别的携带病毒的野猫,前几天感觉叫声不太对劲,没太在意,前天突然呕吐出一些黄绿色的液体,还以为他吃坏肚子了,就想着等等看,到了昨天,感觉小白已经无精打采,上吐下泻,非常虚弱,一早就带他去了宠物医院,住进了动物ICU,保暖,输液。到了晚上的时候,医生说有点精神了,但基本上不会治好了,于是我们就把他带回家与我们度过最后的时光,到家后,他还认得我们,在客厅慢走了几步,就躺下了,我们把他放回窝里,旁边放了吃喝的东西,祈祷他能度过这个难关。

早上女儿醒来第一时间就去看猫咪,发现他已经一动不动了,安慰过她之后,带上小白,把他埋在了一个小树林,希望他能在天堂生活的快乐,没有病痛。

咪咪,我们会想你的!

刚到家里的小白,感觉一切都新鲜
脏脏的小白,还没洗过澡
喜欢跟家人亲近的小白,工作的时候人家就要上键盘
小白原来的家是个农家院,除了一群无忧无虑的猫狗,还有少了一只胳膊的野猴子
也是我们疏于照顾,忽视了前面几天的异常,意识到的时候已经晚了,赶紧带小白去看医生
输液延长了一天的生命,晚上我们把他带回家,还在客厅慢走了几步,半夜的时候就走了
小白离开了我们,我们很难过,把他埋葬在一个风景优美的小树林,希望他在天堂永远快乐