美文网首页
【实战篇】踩坑无数,助你无痛基于docker部署jenkins+

【实战篇】踩坑无数,助你无痛基于docker部署jenkins+

作者: webxiaohua | 来源:发表于2020-06-18 20:07 被阅读0次
    0x01:前提概要

    本篇做了两种方案,一种将AWS-ECR作为镜像仓库来使用,另外一种是自己搭建的Harbor仓库。(测试环境跑在内网虚拟机,线上环境跑在公网AWS云主机,所以采用了两套仓库部署)

    0x02:关键名词解释

    github: 代码托管工具
    docker: 容器操作工具
    kubectl: k8s集群客户端
    jenkins: CI/CD工具
    rancher: k8s集群管理工具
    harbor: docker开源镜像仓库
    aws-cli: AWS命令行工具
    aws-ecr: 亚马逊云镜像仓库

    0x03:服务器资源介绍

    本次实验使用了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以及harbor,整个服务部署情况如下图所示:


    部署结构图.png

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

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

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

    chown -R 1000 home
    chown -R 1000 /root/.docker
    
    0x04:打通AWS服务,安装配置aws-cli (如果走的是Harbor仓库,这步跳过)

    由于我们需要依赖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
    

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

    0x05:安装Harbor (如果走的是AWS-ECR仓库,这步跳过)

    1.进入 /opt/docker/harbor 目录,下载harbor程序包并解压,本次实验用的版本是v1.10.3;
    2.进入harbor目录,找到harbor.yml,修改 hostname、port并且注释掉https访问(没有证书);
    3.执行 ./install.sh ,等待下载镜像,安装完毕以后当前目录下会生成 docker-compose.yml 文件,后面可以通过docker-compose来启停harbor服务;
    4.安装启动完成以后,访问 http://172.100.10.13:9090/ 即可看到harbor登录界面。
    整体的安装步骤可以参考 https://www.jianshu.com/p/6fcacc2020d5
    【注意】这里有个需要注意的坑,因为我们的harbor是用的http协议,在执行docker pull的时候会提示:

    [root@cpe-172-100-10-13 docker]# docker pull 172.100.10.13:9090/test/demo1:20200623102639
    Error response from daemon: Get https://172.100.10.13:9090/v2/: http: server gave HTTP response to HTTPS client
    

    怎么解决该问题,网上目前搜到的方式是加docker配置文件: /etc/docker/daemon.json,文件内容具体如下:

    {
      "insecure-registries": ["172.100.10.13:9090"]
    }
    

    然后重启docker即可。(生产环境还是推荐配置https)

    0x06: 部署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
    
    0x07: 部署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 /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仓库授权配置文件存放于此目录下
    /root/.kube/config # k8s集群配置文件
    

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

    0x08: 通过Jenkins部署golang应用

    我们的代码都托管在github上,首先在github上面创建仓库,然后把编写好的代码提交上去,注意在项目根目录下写好你的Dockerfile和Jenkinsfile,用来制作docker镜像以及jenkins发布的pipeline步骤。我这里是用branchName做环境区分,test分支发布测试环境,master分支发布线上环境。
    Jenkinsfile文件内容:

    // 支持发布 test pro
    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} ."
            echo "branch: ${BRANCH_NAME}"
          }
        }
    
        stage('repository authorization') {
          steps {
            script {
              echo "repository authorization stage ..."
              if(env.BRANCH_NAME == 'test'){
                sh "docker login -u admin -p Harbor12345 172.100.10.13:9090"
              }
              else if(env.BRANCH_NAME == 'master'){
                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 ***** --password-stdin *****.dkr.ecr.*****.amazonaws.com"
              }
            }
          }
        }
    
        stage('build') {
          steps {
            echo "build stage ..."
            sh "docker build -t *****:${params.appVersion} ."
          }
        }
    
        stage('tag') {
          steps {
            script {
              echo "tag stage ..."
              if(env.BRANCH_NAME == 'test'){
                sh "docker tag *****:${params.appVersion} 172.100.10.13:9090/*****/*****:${params.appVersion}"
              }else if(env.BRANCH_NAME == 'master'){
                sh "docker tag *****:${params.appVersion} *****.dkr.ecr.*****.amazonaws.com/*****:${params.appVersion}"
              }
            }
          }
        }
    
        stage('push') {
          steps {
            script {
              echo "push stage ..."
              if(env.BRANCH_NAME == 'test'){
                sh "docker push 172.100.10.13:9090/*****/*****:${params.appVersion}"
              }else if(env.BRANCH_NAME == 'master'){
                sh "docker push *****.dkr.ecr.*****.amazonaws.com/*****:${params.appVersion}"
              }
            }
          }
        }
    
        stage('clean') {
          steps {
            script {
              echo "clean stage ..."
              if(env.BRANCH_NAME == 'test'){
                sh "docker rmi 172.100.10.13:9090/*****/*****:${params.appVersion}"
              }else if(env.BRANCH_NAME == 'master'){
                sh "docker rmi *****.dkr.ecr.*****.amazonaws.com/*****:${params.appVersion}"
              }
              sh "docker rmi *****:${params.appVersion}"
            }
          }
        }
    
        stage('k8s secret') {
          steps {
            script {
              echo "k8s secret stage ..."
              if(env.BRANCH_NAME == 'test' ){
                sh "docker run --rm --name kubectl -w /.kube -v /root/.kube/config:/.kube/config bitnami/kubectl:latest delete --ignore-not-found=true secrets harbor-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 harbor-secret --from-file=.dockerconfigjson=/root/.docker/config.json --type=kubernetes.io/dockerconfigjson"
              }else if(env.BRANCH_NAME == 'master'){
                // 不完美,不能做到无缝升级,查阅了官方文档,没有 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"
              }
            }
            //sh "kubectl delete secrets aws-ecr-secret"
            //sh "kubectl create secret generic aws-ecr-secret --from-file=.dockerconfigjson=/root/.docker/config.json --type=kubernetes.io/dockerconfigjson"
          }
        }
    
        stage('deploy') {
          steps {
            script {
              echo "deploy stage ..."
              if(env.BRANCH_NAME == 'test'){
                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: harbor-secret
          containers:
            # 声明容器名称,注意不是pod名称,pod名称应该定义在metadata中
            - name: *****
              image: 172.100.10.13:9090/*****/*****:${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
                '''
                sh "docker run --rm --name kubectl -w /.kube -v /root/.kube/config:/.kube/config -v /opt/docker/jenkins/home/workspace/*****_test/*****.yaml:/.kube/*****.yaml bitnami/kubectl:latest apply -f ./*****.yaml"
              }else if(env.BRANCH_NAME == 'master'){
                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: ["pro"]
              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
                '''
                sh "docker run --rm --name kubectl -w /.kube -v /root/.kube/config:/.kube/config -v /opt/docker/jenkins/home/workspace/*****_master/*****.yaml:/.kube/*****.yaml bitnami/kubectl:latest apply -f ./*****.yaml"
              }
              //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
    0x09: 总结归纳

    目前整体流程下来基本上没什么大问题,就是还有几个做法不是太完美:
    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

    相关文章

      网友评论

          本文标题:【实战篇】踩坑无数,助你无痛基于docker部署jenkins+

          本文链接:https://www.haomeiwen.com/subject/uqqsxktx.html