踩坑无数,助你无痛基于docker部署jenkins+rancher

0x01:前提概要

公司业务基于AWS云服务,由于测试环境是在公司内网,所以本次实验是在内网环境完成,只是借助了AWS的镜像仓库服务(AWS-ECR)

0x01:关键名词解释

docker: 容器操作工具
kubectl: k8s集群客户端
jenkins: CI/CD工具
rancher: k8s集群管理工具
harbor: docker镜像仓库(本次实验没有选用)
aws-ecr: 亚马逊云镜像仓库

0x02:服务器资源介绍

本次实验使用了2台4c8g的虚拟服务器,操作系统为centos7.3,服务器资源描述如下:
172.100.10.13 【关闭selinux、安装docker、docker-compose、kubectl】
172.100.10.23 【关闭selinux、安装docker】
我们选用172.100.10.13这台服务器作为master节点,它上面会部署jenkins和rancher,整个服务部署情况如下图所示:

部署情况.png

我们预先在172.100.10.13上创建好如下的目录结构:

opt/
├── docker
│   ├── jenkins
│   │   └── home
│   └── rancher_home
│       ├── auditlog
│       └── rancher

注意把/opt/docker/jenkins/home目录用户设置给jenkins用户,否则容器内无法写入该目录,~/.docker 目录也是如此,因为jenkins容器内部需要登录镜像仓库,登录操作是需要修改~/.docker/config.json文件的(参考资料

chown -R 1000 home
chown -R 1000 /root/.docker
0x03:打通AWS服务,安装配置aws-cli

由于我们需要依赖aws的镜像仓库服务,所以需要有能操作aws的客户端工具,AWS官方提供了两种使用方式,一种是在宿主机安装,另外一种直接通过docker来完成,因为宿主机要配置aws,所以我先采用的第一种方式,参考连接:
aws-cli安装
aws-cli配置
通过以上,我们已经可以对aws进行基本的操作,通过以下命令来检测,如果能正常回显说明配置完成。

aws configure get region

配置完成以后我们回到用户目录下,看一下生成了.aws目录,里面包含了两个文件,这里先放一遍,后面我们会用到。

.aws/
├── config
└── credentials

另外一种方式可以通过docker来启动aws-cli,这里我们也可以尝试一下,后面在容器内部使用aws-cli就是用的这种方式:

docker run --rm -it -v /root/.aws:/root/.aws amazon/aws-cli configure get region

可以看到跟上面执行的结果是一样的。

0x04:宿主机登录ECR仓库,其他类型的镜像仓库原理也是一样的

完成aws-cli基础部署之后,我们需要对镜像仓库进行登录授权,这里的登录主要是为了拿到授权文件,为后面jenkins执行pipline提供配置文件,命令如下:

aws ecr get-login-password --region ********** | docker login --username ********** --password-stdin ***************.dkr.ecr.************.amazonaws.com

执行完成会看到 Login Succeed提示,这表示我们已经拿到了aws-ecr仓库的授权,可以进行镜像的推送和下载(请注意,这个登录态是有时间限制的,不同的仓库可能不一样: aws-ecr 登录说明)。同时我们本机的 ~/.docker 目录下也生成了docker的配置文件,打开看一下:

cat ~/.docker/config.json
{
    "auths": {
        "*********.dkr.ecr.********.amazonaws.com": {
            "auth": "**************"
        }
    },
    "HttpHeaders": {
        "User-Agent": "Docker-Client/19.03.11 (linux)"
    }
}

接下来我们就可以在ECR上面创建我们需要的仓库了,可以登录到aws控制台上创建,也可以采用aws-cli,命令如下:

aws ecr create-repository \
    --repository-name ******* \
    --image-scanning-configuration scanOnPush=false \
    --region ******

以上完成后,我们开始安装rancher

0x05: 部署Rancher,创建K8S集群

我们采用docker的方式部署,我这里选择的rancher版本是2.4.3

sudo docker run -d --restart=unless-stopped -v /opt/docker/rancher_home/rancher:/var/lib/rancher/ -v /opt/docker/rancher_home/auditlog:/var/log/auditlog/ --name rancher -p 80:80 -p 443:443 rancher/rancher:v2.4.3

启动成功以后就可以通过浏览器访问了,如果访问不成功的可以耐心等待一会,启动过程需要一点时间,如果长时间没有成功,注意看一下 docker log,有防火墙限制的记得把80和443端口开放出来。
我这里通过访问 https://172.100.10.13:443/ 进入rancher ,选择语言,设置密码,然后就OK了。
这时我们还没有K8S集群,需要来创建一个,看到rancher主菜单有一个“集群”,点进去,找到添加集群按钮

image.png

由于我们是自己的服务器搭建集群,所以这里选择自定义,进去以后输入集群名称,进入下一步,

image.png

这一步把Etcd和Control都选上,然后复制下面的命令,到172.100.10.13服务器上面执行,没有报错的话就静静等待集群启动起来。
启动完成以后我们查看新创建的集群,看到状态已经是绿色的Active了,这个时候可以给集群添加主机,点击集群升级按钮,打开页面后滚动到最下方,找到添加主机命令,复制。

image.png

登录到172.100.10.23服务器,执行上面的命令,完成以后节点会自动加入集群。

image.png

集群创建好了,我们还需要一个命令行客户端来管理它,这里用到了kubectl,我们来配置一下,首先拿到kubeconfig文件,在这个位置:

image.png

复制好内容,保存到172.100.10.13服务器的 /root/.kube/config文件中,然后测试一下看看是不是生效

kubectl get pods --all-namespaces 

或者利用docker操作kubectl

docker run --rm --name kubectl -v /root/.kube/config:/.kube/config bitnami/kubectl:latest get pods --all-namespaces
0x06: 部署Jenkins

docker启动jenkins脚本:

docker run -d -p 8081:8080 -p 50000:50000 \
-v /opt/docker/jenkins/home:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /usr/bin/docker:/usr/bin/docker \
-v /usr/lib64/libltdl.so.7:/usr/lib/x86_64-linux-gnu/libltdl.so.7 \
-v /root/.aws:/root/.aws \
-v /root/.docker:/root/.docker \
-v /usr/bin/kubectl:/usr/bin/kubectl \
-v /root/.kube/config:/root/.kube/config \
-u root --name jenkins --restart=always --privileged=true jenkins/jenkins

看到我们挂载了好多宿主机上的文件,这里简单介绍一下:

/opt/docker/jenkins/home # jenkins 工作的主要目录,用于保存jenkins状态数据;
/var/run/docker.sock && /usr/bin/docker && /usr/lib64/libltdl.so.7  # 用于在容器内部调用宿主机docker命令;
/root/.aws # aws-cli配置文件
/root/.docker # docker仓库授权配置文件存放于此目录下
/usr/bin/kubectl  # 用于在容器内部调用宿主机kubectl命令
/root/.kube/config # k8s集群配置文件

启动完成以后,访问 http://172.100.10.13:8081/ ,按照引导进入jenkins。
接下来开始安装jenkins插件,这里我们主要用到了这些插件:
Localization: Chinese 、Git、GitHub、ssh、Blue Ocean、docker、docker-build-step、Pipeline

0x07: 通过Jenkins部署golang应用

我们的代码都托管在github上,首先在github上面创建仓库,然后把编写好的代码提交上去,注意在项目根目录下写好你的Dockerfile和Jenkinsfile,用来制作docker镜像以及jenkins发布的pipeline步骤
Jenkinsfile文件内容:

def createVersion() {
    // 定义一个版本号作为当次构建的版本,输出结果 20200615175842
    return new Date().format('yyyyMMddHHmmss')
}
def currentVersion = createVersion()
pipeline {
  agent any
  parameters {
    string(
        name: 'appVersion',
        defaultValue: currentVersion,
        description: '应用版本号'
    )
  }
  stages {
    stage('prepare') {
      steps {
        echo "workspace: ${WORKSPACE}"
        echo "GIT_COMMIT: ${GIT_COMMIT}"
        echo "APP_VERSION: ${params.appVersion} ."
      }
    }

    stage('repository authorization') {
      steps {
        echo "repository authorization stage ..."
        sh "docker run --rm -v /root/.aws:/root/.aws amazon/aws-cli ecr get-login-password >> ecr-login.txt"
        sh "cat ecr-login.txt | docker login --username AWS --password-stdin *****.dkr.ecr.*****.amazonaws.com"
      }
    }

    stage('build') {
      steps {
        echo "build stage ..."
        sh "docker build -t *****:${params.appVersion} ."
      }
    }

    stage('tag') {
      steps {
        echo "tag stage ..."
        sh "docker tag *****:${params.appVersion} *****.dkr.ecr.*****.amazonaws.com/******:${params.appVersion}"
      }
    }

    stage('push') {
      steps {
        echo "push stage ..."
        sh "docker push *****.dkr.ecr.*****.amazonaws.com/******:${params.appVersion}"
      }
    }

    stage('clean') {
      steps {
        echo "clean stage ..."
        sh "docker rmi ******.dkr.ecr.*****.amazonaws.com/*****:${params.appVersion}"
        sh "docker rmi *****:${params.appVersion}"
      }
    }

    stage('k8s secret') {
      steps {
        echo "k8s secret stage ..."
        // 不完美,不能做到无缝升级,查阅了官方文档,没有 exist for update 机制
        sh "docker run --rm --name kubectl -w /.kube -v /root/.kube/config:/.kube/config bitnami/kubectl:latest delete --ignore-not-found=true secrets aws-ecr-secret"
        sh "docker run --rm -u root --name kubectl -w /.kube -v /root/.kube/config:/.kube/config -v /root/.docker/config.json:/root/.docker/config.json bitnami/kubectl:latest create secret generic aws-ecr-secret --from-file=.dockerconfigjson=/root/.docker/config.json --type=kubernetes.io/dockerconfigjson"
      }
    }

    stage('deploy') {
      steps {
        echo "deploy stage ..."
        sh '''
        cat > *****.yaml << EOF
# 声明一个Deployment资源对象
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-*****
spec:
  # 通过replicas声明pod个数是2
  replicas: 2
  # 通过标签选择被控制的pod
  selector:
    matchLabels:
      app: *****
  # 在template中定义pod
  template:
    metadata:
      labels:
        # 给pod打上标签app=*****
        app: *****
    spec:
      imagePullSecrets:
        - name: aws-ecr-secret
      containers:
        # 声明容器名称,注意不是pod名称,pod名称应该定义在metadata中
        - name: *****
          image: *****.dkr.ecr.*****.amazonaws.com/*****:${appVersion}
          args: ["test"]
          ports:
            - name: http
              containerPort: 8898
          resources:
            limits:
              cpu: 1000m
              memory: 2Gi
            requests:
              cpu: 500m
              memory: 1Gi
# 在一个yaml文件中通过---分割多个资源对象
---
apiVersion: v1
# 声明一个Service资源对象
kind: Service
metadata:
  name: service-*****
spec:
  ports:
    - name: http
      # Service监听端口
      port: 8898
      # 转发到后端Pod的端口号
      targetPort: 8898
      # 外部访问端口
      #nodePort: 30076
  # service-*****将选择标签包含app=*****的pod
  selector:
    app: *****
  type: NodePort
EOF
        '''
        // 不完美,还是需要依赖宿主机的 kubectl
        //sh "docker run --rm --name kubectl -w /.kube -v /root/.kube/config:/.kube/config -v ${WORKSPACE}/*****.yaml:/.kube/*****.yaml bitnami/kubectl:latest apply -f ./*****.yaml"
        sh "kubectl apply -f *****.yaml"
      }
    }

  }
}

下面我们来部署应用,打开Blue Ocean,进去之后点击“创建流水线”,代码仓库选择GitHub,首次会提示你输入access_token,输入进去即可,下一步选择组织,接着选择项目仓库,最后点击创建流水线,完成以后就会按照项目中配置的Jenkinsfile开始执行发布流程,我们上面分拆了这几个步骤:预备、编译打包镜像、生成tag、推送镜像、部署镜像、清理镜像,当然这些不是固定的,可以按照你的需求自由增减。

image.png
0x08: 总结归纳

目前整体流程下来基本上没什么大问题,就是还有几个做法不是太完美:
1.部署jenkins依赖宿主机的docker,最好能在jenkins容器内直接操作docker;
2.kubectl也是同样的问题,我试了kubtctl镜像,在宿主环境下可以执行kubectl apply,但是到container环境中会报错,已经反馈issue给到作者,看看后面能不能解决;https://github.com/bitnami/bitnami-docker-kubectl/issues/16
3.Jenkinsfile 因为用到了参数化构建,第一次会构建错误,镜像拿不到版本号,但是第二次以后就都可以了,不知道是何原因

image.png

发表评论

电子邮件地址不会被公开。 必填项已用*标注