Docker容器搭建 Jenkines CI/CD踩坑记

96
神罗天征_39a0
0.1 2018.10.20 20:14* 字数 1806

前言

周末闲来无事,尝试用 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的搭建 !!!

总结

  1. 上面最终的解决方案虽然成功了,但实际上运行的 Jenkins 并不是一个容器,并且我的 jenkinsfile 也不需要 使用 agent { docker { xxx }} 这种语法了,这跟在宿主机上安装 Jenkins 运行 CI/CD 没有太大差别。

  2. 虽然使用 Docker 容器运行一些单独的应用,非常方便,也能够随时保持宿主机的环境干净,但在一刻复杂的使用场景下还是会遇到各种未知的坑。

技术文档
Web note ad 1