Jenkins 持续集成


今天看到《Jenkins2 权威指南》出来了,马上买了一本,DevOps 相关的书籍更新太快了,这包括 Docker、Jenkins,新特性快速推出,旧版本必然快速淘汰,可以看一下 Jenkins 的版本,Jenkins 1.x 相关书籍已经严重滞后了。

1   基础

1.1   安装

  1. 作为独立的应用程序安装:(1)二进制包安装,(2)brew、yum 安装,(3)直接下载 war 包,java -jar jenkins.war。 更新:用 brew、yum 命令更新,或者用新版的 jenkins.war 替换旧版然后重启
  2. 应用服务器安装,比如 tomcat 更新:注意要删除一些缓存,避免过期页面被引用

1.2   主目录

不管你把 Jenkins 的 war 文件存放在哪儿,Jenkins 都把其所有重要的数据存放在一个专用的、隔离的,称为 Jenkins 主目录的目录下。在这里,Jenkins 存储关于构建服务器的配置信息、构建作业、构建产物和其他有用的信息,当然也包括你安装了的任何插件。且 Jenkins 主目录格式是跨版本向后兼容的,所以你可以自由地更新或者重新安装 Jenkins 而不影响 Jenkins 主目录。

一般主目录是 ~/.jenkins 目录,一个好的做法是为 Jenkins 创建一个特殊的用户和用户组。sudo groupadd buildsudo useradd --create-home --shell /bin/bash --groups build jenkins,让主目录在用户空间下。 确保你的 Jenkins 主目录能定期备份是非常重要的,Jenkins 程序本身相对并不那么重要。

1.3   节点

节点代表了任何可以执行 Jenkins 任务的系统,包括了主节点,代理节点和容器,前两都是机器,后者是需要在机器上运行的容器,但都可以称为节点,从这点可以看出,节点是承载任务的,而不是节点机器。

1.4   凭证

1.5   Pipeline

Jenkins 2 的新特性,通过插件获得流水线这一新功能,通过 Jenkins DSL 实现流水线代码,代码提取到一个 Jenkinsfile 文件中。

2   声明式流水线

创建一个流水线项目后,项目左侧列表会有“流水线语法”链接,可以借助这一脚手架直接生成 DSL 代码,比如与“构建触发”相关的可以选择 properties 步骤生成。当然想要理解声明式还得系统性地学习。

3   构建触发器

除了手动立即构建,还可以构建触发器自动的构建,这也是自动化部署的基础,常用的几种构建触发器如下:

4   通知

7   分布式部署实战

下面通过 vagrant 进行分布式部署,主要还是用于开发和测试使用,本次实战只部署机器和环境,采用手动运行 jenkins.war。首先来看一下目录结构:

vagjenkins/
├── Vagrantfile
├── certs
│   ├── domain.crt
│   └── domain.key
├── jdk-8u211-linux-x64.tar.gz
├── jenkins.war
└── scripts
    ├── docker.sh
    ├── hosts.sh
    ├── java.sh
    └── registry.sh

7.1    Vagrantfile

Vagrantfile 就是基础设置即代码文件,可以将上述的过程代码化:

cat <<EOF >> /Users/ada/vagjenkins/Vagrantfile
# -*- mode: ruby -*-
# vi:set ft=ruby sw=2 ts=2 sts=2:

NODE_COUNT = 2
POD_NETWORK_CIDR = "10.244.0.0/16"

MASTER_IP = "192.168.56.113"
NODE_IP_PREFIX = "192.168.56."

Vagrant.configure("2") do |config|
  config.vm.box = "centos/7"
  config.vm.box_version = "1902.01"

  config.vm.provider "virtualbox" do |v|
    v.memory = 1536
    v.cpus = 2
  end

  config.vm.provision "install-docker", type: "shell", :path => "scripts/docker.sh"
  config.vm.provision "setup-hosts", type: "shell", :path => "scripts/hosts.sh" do |s|
    s.args = ["eth1"]
  end


  config.vm.define "jenkins-master" do |node|
    node.vm.hostname = "jenkins-master"
    node.vm.network :private_network, ip: MASTER_IP

    node.vm.network "forwarded_port", guest: 8080, host: 8080

    node.vm.provision "install-registry", type: "shell", :path => "scripts/registry.sh"
    node.vm.provision "install-java", type: "shell", :path => "scripts/java.sh"

  end


  (1..NODE_COUNT).each do |i|
     config.vm.define "jenkins-node0#{i}" do |node|
        node.vm.hostname = "jenkins-node0#{i}"
        node.vm.network :private_network, ip: NODE_IP_PREFIX + "#{113 + i}"

     end
  end

  # config all node to connect registry
  config.vm.provision "shell", inline: <<-SHELL
        mkdir -p /etc/docker/certs.d/192.168.56.113:5000
        cp /vagrant/certs/domain.crt /etc/docker/certs.d/192.168.56.113:5000/ca.crt
    SHELL

end
EOF

7.2   scripts

  1. Docker 安装
cat <<EOF >> /Users/ada/vagjenkins/scripts/docker.sh
#!/bin/bash

set -ex

# 安装 docker

yum install -y yum-utils \
  device-mapper-persistent-data \
  lvm2
yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo
yum install -y docker-ce docker-ce-cli containerd.io

# docker 官方镜像阿里云加速

mkdir -p /etc/docker
tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://r11fpimm.mirror.aliyuncs.com"]
}
EOF

# 启动

usermod -aG docker vagrant 
systemctl start docker
systemctl enable docker
EOF
  1. 设置主机名
cat <<EOF >> /Users/ada/vagjenkins/scripts/hosts.sh
#!/bin/bash

set -xe

IFNAME=$1
IP="$(ip -4 a s $IFNAME | grep "inet" | head -1 |awk '{print $2}' | cut -d'/' -f1)"
sed -e "s/^.*${HOSTNAME}.*/${IP} ${HOSTNAME} ${HOSTNAME}/" -i /etc/hosts
EOF
  1. 启动 docker registry 容器
cat <<EOF >> /Users/ada/vagjenkins/scripts/registry.sh
#!/bin/bash

set -ex

# run container

docker pull registry:2.7.1
docker run -d \
  --restart=always \
  --name registry \
  -v `pwd`/registry:/var/lib/registry \
  -v /vagrant/certs:/certs \
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
  -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
  -p 5000:5000 \
  registry:2.7.1

# setup firewall, 因为 vagrant 镜像默认防火墙是关闭的,所以不用设置

#firewall-cmd --permanent --add-port=8080/tcp
#firewall-cmd --reload
EOF
  1. 安装 jdk 和 git,建议用 ssh 连接 git,一般有两种办法

我这里选择了第二种方式,注意设置环境变量的 $ 符号不要被解析掉:


cat <<EOF >> /Users/ada/vagjenkins/scripts/java.sh
#!/bin/bash

set -ex

# untar jdk

mkdir -p /usr/local/java
tar -zxf /vagrant/jdk-8u211-linux-x64.tar.gz -C /usr/local/java/

# set env

cat <<EOF >> /home/vagrant/.bash_profile
export JAVA_HOME=/usr/local/java/jdk1.8.0_211
export JRE_HOME=\${JAVA_HOME}/jre
export CLASSPATH=.:\${JAVA_HOME}/lib:\${JRE_HOME}/lib
export PATH=\${JAVA_HOME}/bin:\$PATH
EOF

yum install -y git
EOF

7.3 运行 master 节点的 Jenkins

完成以上步骤,启动虚拟机,用 java -jar /vagrant/jenkins.war 运行 Jenkins,因为 8080 已经映射到宿主机,所以可以在宿主机访问 Jenkins:localhost:8080。

7.4 加入 node 从节点

这里主要就把从节点的 ssh 私钥(服务器鉴权放在 /home/vagrant/.ssh/authorized_keys)生成 jenkins 的凭据,节点的 ssh 私钥可以通过 vagrant ssh [name] –debug 的输出日志中获取,一般放在 /Users/ada/vagjenkins/.vagrant/machines/[name]/virtualbox/private_key,通过 ssh、scp 去连接都需要该 private_key,例子如下:

主节点通过 ssh 连接到从节点,同样需要 known_hosts 验证,在可以在服务器手动执行 ssh vagrant@192.168.56.115 验证再到 jenkins 启动节点。

8 Troubleshooting

参考文献 [1] what-is-scrum. https://www.scrum.org/resources/what-is-scrum [2] 如何从零开始搭建 CI/CD 流水线 https://www.infoq.cn/article/WHt0wFMDRrBU-dtkh1Xp