前言
周末闲来无事,尝试用 docker 搭建一个真实项目的CI/CD,作为非专业docker用户,在这个过程中遇到不少坑,到最后我选择了放弃,因为最终的结果让我并不满意,感觉跟传统的在宿主机上搭建差别并不大。 当然这可能是我个人对docker的了解还不够深入,如果对于我遇到的问题你有好的解决办法,或者说我走在了一条完全错误的解决道路上,还请联系我加以指正,必定万分感谢!
要达到的目标
项目包含一个Srping boot工程,两个React工程(其中一个使用ansible部署,另一个使用传统shell脚本部署),使用Piplie as code(jenkinsfile)的方式来进行CI/CD搭建,因此需要jenkins的运行环境包含Java, Nodejs, Shell等工具。
操作环境
阿里云ECS, Ubuntu16.04
踩坑步骤
1. 在ubuntu上安装 Docker
sudo apt-get update
apt-cache policy docker-ce
sudo apt-get install -y docker-ce
sudo systemctl status docker
2. 启动一个Jenkins容器
这里我选择了jenkinsci/jenkins:latest 这个镜像,你也可以使用 BlueOcean 版本
docker pull jenkinsci/jenkins
docker run -u root -idt --name jenkins -p 50001:50000 -p 8080:8080 \
-v /var/jenkins_home:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $(which docker):/usr/bin/docker \
-v /usr/lib/x86_64-linux-gnu/libltdl.so.7:/usr/lib/x86_64-linux-gnu/libltdl.so.7 \
jenkinsci/jenkins
注意这里的 -v /var/run/docker.sock:/var/run/docker.sock
和 -v /usr/lib/x86_64-linux-gnu/libltdl.so.7:/usr/lib/x86_64-linux-gnu/libltdl.so.7
是必不可少的哦,否则你会遇到以下报错:
jenkins pipeline agent docker : not found
和
docker: error while loading shared libraries: libltdl.so.7: cannot open shared object file: No such file or directory
当然,如果你使用的是阿里云ECS,你需要到安全组设置中将
8080
端口开放出来,否则外部是无法访问的
3. 安装Jenkins插件
在浏览器中访问 http://ip_address:8080
,进入Jenkins控制台,页面会要求你输入初始密码,在提示页面上给出的路径下/var/lib/jenkins/secrets/initialAdminPassword
找到密码并粘贴,进入下一步开始安装各种默认的插件,要使用 Groovy 语法编写的 Jenkinsfile,你需要
安装Pipeline Utility Steps
这个插件,否则CI在运行过程无法识别各种 Groovy 标签。
接着就是问题所在了~
4. 运行CI
最初的 jenkinsfile 大概是这样的:
pipeline {
agent any
stages {
stage("Build") {
steps {
script {
sh "npm install"
.......
sh "npm run build:dev"
......
}
}
}
stage("Test") {......}
stage("Deploy to DEV") {......}
}
......
}
def deploy(hostNames, env) {
......
sh "ssh root@${host} \"mkdir -p /destination\""
......
}
这是一个在ubuntu系统上运行的脚本,在 Jenkins 容器里是无法正常运行的。因为 Jenkins 中并没有Nodejs 和 Shell 环境,所以需要在不同的 Stage 使用不同环境的 Docker 容易, 于是将脚本改成:
pipeline {
agent none
stages {
stage("Build") {
agent { -------------> 使用 node 容器
docker { image 'node:7-alpine' }
}
steps {
script {
sh "npm install"
.......
sh "npm run build:dev"
......
}
}
}
stage("Test") {......}
stage("Deploy to DEV") {
agent { ----------> 使用 ubuntu 容器
docker { image 'ubuntu' }
}
......
}
}
......
}
def deploy(hostNames, env) {
......
sh "ssh root@${host} \"mkdir -p /destination\""
......
}
现在编译阶段能正常运行了~
但遇到的第一个痛苦的问题是,npm 默认的registry 在 Jenkins 容器中下载速度实在是太慢,下载了一个多小时都没完成
npm config get registry ------> 查看npm registry
https://registry.npmjs.org/ -----> 默认 registry
于是切换成淘宝的registry
npm config set registry https://registry.npm.taobao.org/
很快很快!!
可是接着在部署阶段,我们虽然使用了 ubuntu 容器,可这个容器里居然没有默认自带 ssh
命令。于是只有自己写一个 Dockefile:
FROM ubuntu:16.04
RUN apt-get update && apt-get install -y openssh-server
RUN mkdir /var/run/sshd
RUN echo 'root:screencast' | chpasswd
RUN sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
# SSH login fix. Otherwise user is kicked off after login
RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
ENV NOTVISIBLE "in users profile"
RUN echo "export VISIBLE=now" >> /etc/profile
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]
编译
docker build -t customized_ubuntu .
然后 Jenkinsfile 的部署阶段改成以下容器:
agent { ----------> 使用 customized_ubuntu 容器
docker { image 'customized_ubuntu' }
}
这样总该没问题了吧?? 然后天雷滚滚,问题又来了~
在执行 sh "ssh root@${host} \"mkdir -p /destination\""
时,会告诉你
Host key verification failed.
然而,ssh 没有 -p 这种输入密码的参数~ 那么就想想解决办法吧!
理论上这里有两种方式解决:
手动在执行到该步骤时输入目标机密码,可这显然违背了是 CI/CD 自动化运行到最基本原则,显然不现实
使用Expect脚本输入密码,可行,但是需要为每一条远程连接命令(ssh, scp) 都写一个脚本用以输入密码,
实在太过于麻烦,放弃~
将容器的ssh-key 添加到宿主机的
authorized_keys
当中即可实现免密码登录,但这个容器只是一个临时容器,每次运行都会产生不同的容器,所以就自然没有固定的ssh-key,头痛~~~在容器中安装
sshpass
工具,在使用 ssh 命令时显示的将密码作为参数传入,用法如下:sshpass -p passowrd ssh root@ip_address
因此,用于自定义 ubuntu 镜像的 Dockerfile 改成(增加了sshpass 的安装)
FROM ubuntu:16.04 RUN apt-get update && apt-get install -y openssh-server \ && apt-get install sshpass RUN mkdir /var/run/sshd RUN echo 'root:screencast' | chpasswd RUN sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config # SSH login fix. Otherwise user is kicked off after login RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd ENV NOTVISIBLE "in users profile" RUN echo "export VISIBLE=now" >> /etc/profile EXPOSE 22 CMD ["/usr/sbin/sshd", "-D"]
然后再编译容器 image 之后运行CI,呵呵,天雷滚滚,sshpass 连接阿里云ECS居然一直提示
Permission denied, please try again.
,但却能连接到我的另一台Mac电脑上,这里我会接着查明原因并补充答案~
至此,我放弃了单独使用一个 Jenkins 容器搭建CI的尝试~
接下来的尝试
基于 ubuntu image,自定义一个带有完全运行环境的 ubuntu image,将各种工具和 Jenkins 安装到 ubuntu容器里
FROM ubuntu:16.04 RUN wget -q -O - http://pkg.jenkins-ci.org/debian/jenkins-ci.org.key | apt-key add - \ && sh -c 'echo deb http://pkg.jenkins-ci.org/debian binary/ > /etc/apt/sources.list.d/jenkins.list' \ && apt-get update \ && apt-get install jenkins \ && apt-get update && apt-get install -y openssh-server \ && apt install openjdk-8-jre-headless -y \ && apt-get install nodejs -y \ && apt-get install npm -y \ && npm install yarn -g -y \ && ln -s /usr/local/lib/node_modules/yarn/bin/yarn.js /usr/bin/yarn RUN mkdir /var/run/sshd RUN echo 'root:screencast' | chpasswd RUN sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config # SSH login fix. Otherwise user is kicked off after login RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd ENV NOTVISIBLE "in users profile" RUN echo "export VISIBLE=now" >> /etc/profile EXPOSE 22 CMD ["/usr/sbin/sshd", "-D"]
运行这个容器的时候,相当于在宿主机上再运行一个 ubuntu 虚拟机,在这个虚拟机中运行 Jenkins。但我想说官方这个 Jenkins 镜像下载速度实在是太慢了~ 慢得吐血!
于是,进入容器
docker exec -it container_name bash
,将在上海大学开源镜像站手动下载了安装文件拷贝进来,安装:dpkg -i jenkins_****_all.deb 有可能中途会遇到一些依赖库错误,解决办法: sudo apt-get -f install Jenkins 命令 : service jenkins start/stop/restart/status 修改端口: vim /etc/default/jenkins 将其中的HTTP_PORT =8080修改为其它端口,再重启jenkins服务
最终成功运行 Jenkins 并完成了CI/CD的搭建 !!!
总结
上面最终的解决方案虽然成功了,但实际上运行的 Jenkins 并不是一个容器,并且我的 jenkinsfile 也不需要 使用
agent { docker { xxx }}
这种语法了,这跟在宿主机上安装 Jenkins 运行 CI/CD 没有太大差别。虽然使用 Docker 容器运行一些单独的应用,非常方便,也能够随时保持宿主机的环境干净,但在一刻复杂的使用场景下还是会遇到各种未知的坑。