gitea+drone+kubernetes搭建devops平台

DevOps基本介绍

DevOps 一词的来自于 Development 和 Operations 的组合,突出重视软件开发人员和运维人员的沟通合作,通过自动化流程来使得软件构建、测试、发布更加快捷、频繁和可靠。DevOps 其实包含了三个部分:开发、测试和运维。换句话 DevOps 希望做到的是软件产品交付过程中IT工具链的打通,使得各个团队减少时间损耗,更加高效地协同工作。

关于devops的基础概念可以参考关于DevOps落地方案的个人观点了解,一般来说一次版本发布包含如下Checklist:

  • 代码的下载构建及编译
  • 运行单元测试,生成单元测试报告及覆盖率报告等
  • 在测试环境对当前版本进行测试
  • 为待发布的代码打上版本号
  • 编写 ChangeLog 说明当前版本所涉及的修改
  • 构建 Docker 镜像
  • 将 Docker 镜像推送到镜像仓库
  • 在预发布环境测试当前版本
  • 正式发布到生产环境

单人开发模式

  • clone 项目到本地, 修改项目代码, 如将 Hello World 改为 Hello World V2。
  • git add .,然后书写符合约定的 commit 并提交代码, git commit -m "feature: hello world v2”
  • 推送代码到代码库git push,等待数分钟后,开发人员会看到单元测试结果,gitea 仓库会产生一次新版本的 release,release 内容为当前版本的 changeLog, 同时线上已经完成了新功能的发布。

虽然在开发者看来,一次发布简单到只需 3 个指令,但背后经过了如下的若干次交互,这是一次发布实际产生交互的时序图,具体每个环节如何工作将在后面中详细说明。

多人开发模式:

  • Clone 项目到本地,创建一个分支来完成新功能的开发, git checkout -b feature/hello-world-v3。在这个分支修改一些代码,比如将Hello World V2修改为Hello World V3
  • git add .,书写符合规范的 Commit 并提交代码, git commit -m "feature: hello world v3”
  • 将代码推送到代码库的对应分支, git push origin feature/hello-world
  • 如果功能已经开发完毕,可以向 Master 分支发起一个 Pull Request,并让项目的负责人 Code Review
  • Review 通过后,项目负责人将分支合并入主干,Github 仓库会产生一次新版本的 release,同时线上已经完成了新功能的发布。

这个流程相比单人开发来多了 2 个环节,很适用于小团队合作,不仅强制加入了 Code Review 把控代码质量,同时也避免新人的不规范行为对发布带来影响。实际项目中,可以在 Gitea 的设置界面对 master 分支设置写入保护,这样就从根本上杜绝了误操作的可能。当然如果团队中都是熟手,就无需如此谨慎,每个人都可以负责 PR 的合并,从而进一步提升效率。

devops基础架构 devops平台搭建_nginx

GitFlow 开发模式:

在更大的项目中,参与的角色更多,一般会有开发、测试、运维几种角色的划分;还会划分出开发环境、测试环境、预发布环境、生产环境等用于代码的验证和测试;同时还会有多个功能会在同一时间并行开发。可想而知 CI 的流程也会进一步复杂。

能比较好应对这种复杂性的,首选 GitFlow 工作流, 即通过并行两个长期分支的方式规范代码的提交。而如果使用了 Gitea,由于有非常好用的 Pull Request 功能,可以将 GitFlow 进行一定程度的简化,最终有这样的工作流:

devops基础架构 devops平台搭建_mysql_02

这个模式主要遵循以下约定

  • 以 dev 为主开发分支,master 为发布分支
  • 开发人员始终从 dev 创建自己的分支,如 feature-a
  • feature-a 开发完毕后创建 PR 到 dev 分支,并进行 code review
  • review 后 feature-a 的新功能被合并入 dev,如有多个并行功能亦然
  • 待当前开发周期内所有功能都合并入 dev 后,从 dev 创建 PR 到 master
  • dev 合并入 master,并创建一个新的 release

上述是从 Git 分支角度看代码仓库发生的变化,实际在开发人员视角里,工作流程是怎样的呢。假设我是项目的一名开发人员,今天开始一期新功能的开发:

  1. Clone 项目到本地,git checkout dev。从 dev 创建一个分支来完成新功能的开发, git checkout -b feature/feature-a。在这个分支修改一些代码,比如将Hello World V3修改为Hello World Feature A
  2. git add .,书写符合规范的 Commit 并提交代码, git commit -m “feature: hello world feature A”
  3. 将代码推送到代码库的对应分支, git push origin feature/feature-a:feature/feature-a
  4. 由于分支是以feature/命名的,因此 CI 会运行单元测试,并自动构建一个当前分支的镜像,发布到测试环境,并自动配置一个当前分支的域名如 test-featue-a.avnpc.com
  5. 联系产品及测试同学在测试环境验证并完善新功能
  6. 功能通过验收后发起 PR 到 dev 分支,由 Leader 进行 code review
  7. Code Review 通过后,Leader 合并当前 PR,此时 CI 会运行单元测试,构建镜像,并发布到测试环境
  8. 此时 dev 分支有可能已经积累了若干个功能,可以访问测试环境对应 dev 分支的域名,如 test.avnpc.com,进行集成测试。
  9. 集成测试完成后,由运维同学从 Dev 发起一个 PR 到 Master 分支,此时会 CI 会运行单元测试,构建镜像,并发布到预发布环境
  10. 测试人员在预发布环境下再次验证功能,团队做上线前的其他准备工作
  11. 运维同学合并 PR,CI 将为本次发布的代码及镜像自动打上版本号并书写 ChangeLog,同时发布到生产环境。

由此就完成了上文中 Checklist 所需的所有工作。虽然描述起来看似冗长,但不难发现实际作为开发人员,并没有任何复杂的操作,流程化的部分全部由 CI 完成,开发人员只需要关注自己的核心任务:按照工作流规范,写好代码,写好 Commit,提交代码即可。

总结

  • 如果搭建了Gerrit服务,可以使用Gerrit作为ReviewCode的中间代码仓库
  • golang静态分析工具有sonarqube和golangci-lint等,运行静态分析可以保证代码没有明显的错误。

使用docker-compose实现CI

具体的代码库可以访问drone_gitea_devops仓库查看,这部分只实现了持续集成,并未实现持续发布和持续部署。

代码管理

git基本使用

详情访问赵布的学习笔记-Git的使用查看

gitea部署代码仓库

采用如下docker-compose部署:

version: "3.7"

networks:
  dronenet:
    name: dronenet
services:
  # 使用nginx做反向代理
  # nginx:
  #   image: nginx:alpine
  #   container_name: drone_nginx
  #   ports:
  #     - "8080:80"
  #   restart: always
  #   networks:
  #     - dronenet
  #   volumes:
  #     - ./nginx/conf:/etc/nginx/conf.d:rw
  #     - ./nginx/nginx.conf:/etc/nginx/nginx.conf
  #     - ./nginx/hosts:/etc/hosts:rw

  # gitea 服务
  gitea_server:
    image: gitea/gitea:latest
    ports:
      - 3000:3000
      # 必须映射22端口,不然只能使用http cloen仓库
      - "3022:22"
    volumes:
      - ./gitea_server/data:/data
      - ./gitea_server/conf/app.ini:/data/gitea/conf/app.ini
    restart: always
    container_name: gitea_server
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - HTTP_PORT=3000
      - DB_TYPE=postgres
      - DB_HOST=gitea_postgres:5432
      - DB_USER=${POSTGRES_USER}
      - DB_NAME=${POSTGRES_DB}
      - DB_PASSWD={POSTGRES_PASSWORD}
      - APP_NAME="DROG (gitea + drone) test"
      # this exposes to end users
      - ROOT_URL=http://${HOST}:3000
    networks:
      dronenet:

  # gitea 可以用postgres也可以用mysql
  gitea_postgres:
    image: postgres:alpine
    # ports:
    #   - 5432:5432
    restart: always
    container_name: gitea_postgres
    volumes:
      - ./gitea_postgres/data:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${POSTGRES_DB}
    networks:
      dronenet:
gitea部署需要注意点:
  • gitea可以使用postgress也可使用mysql,但是mysql在映射volume时如果想映射到当前目录,会出现读写问题,具体的问题,可以试一下就会明白
  • ports端口映射时需要映射两个端口,一个是默认的3000,可以使用http/https协议进行git clone,还有需要映射内部的22端口,才能使用git协议
创建drone使用的token

访问http://192.168.0.90:3000/登录gitea,点击个人信息,应用,然后新建一个oAuth2应用程序,名称随便填,重定向url填http://192.168.0.90:8080/login

devops基础架构 devops平台搭建_git_03

然后记录下客户端id,客户端密匙

devops基础架构 devops平台搭建_nginx_04

修改.env文件中的

DRONE_GITEA_CLIENT_ID=dd94066a-df64-4d0c-b49c-e4c2d50bb025
DRONE_GITEA_CLIENT_SECRET=ZmYdYjtaLYLF8fiGYMCBistpQeHuxtyDdQgo3A5dnlY=

然后启动drone容器

Harbor镜像仓库

在drone的pipline中build完镜像后需要提交镜像到images仓库。在持续发布和持续部署阶段需要用到

按照docker-compose-manager仓库里面的harbor目录里的docker-compose启动Harbor镜像仓库,访问http://192.168.0.90:8180/登录harbor,并且记录用户名和密码

devops基础架构 devops平台搭建_devops基础架构_05

使用Drone实现CI

启动Drone容器
version: "3.7"

networks:
  dronenet:
    name: dronenet
# volumes:
#   portainerdata:
#     name: portainerdata

services:
  # 使用nginx做反向代理
  # nginx:
  #   image: nginx:alpine
  #   container_name: drone_nginx
  #   ports:
  #     - "8080:80"
  #   restart: always
  #   networks:
  #     - dronenet
  #   volumes:
  #     - ./nginx/conf:/etc/nginx/conf.d:rw
  #     - ./nginx/nginx.conf:/etc/nginx/nginx.conf
  #     - ./nginx/hosts:/etc/hosts:rw

  # gitea 服务
  gitea_server:
    image: gitea/gitea:latest
    ports:
      - 3000:3000
      # 必须映射22端口,不然只能使用http cloen仓库
      - "3022:22"
    volumes:
      - ./gitea_server/data:/data
      - ./gitea_server/conf/app.ini:/data/gitea/conf/app.ini
    restart: always
    container_name: gitea_server
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - HTTP_PORT=3000
      - DB_TYPE=postgres
      - DB_HOST=gitea_postgres:5432
      - DB_USER=${POSTGRES_USER}
      - DB_NAME=${POSTGRES_DB}
      - DB_PASSWD={POSTGRES_PASSWORD}
      - APP_NAME="DROG (gitea + drone) test"
      # this exposes to end users
      - ROOT_URL=http://${HOST}:3000
    networks:
      dronenet:

  # gitea 可以用postgres也可以用mysql
  gitea_postgres:
    image: postgres:alpine
    # ports:
    #   - 5432:5432
    restart: always
    container_name: gitea_postgres
    volumes:
      - ./gitea_postgres/data:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${POSTGRES_DB}
    networks:
      dronenet:

  # drone使用mysql
  drone_mysql:
    image: mysql
    restart: always
    container_name: drone_mysql
    # ports:
    #   - 3306:3306
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - MYSQL_DATABASE=${MYSQL_DATABASE}
      - MYSQL_USER=${MYSQL_USER}
      - MYSQL_PASSWORD=${MYSQL_PASSWORD}
    networks:
      dronenet:
    volumes:
      - ./drone_mysql/conf/my.cnf:/etc/mysql/my.cnf:rw
      - ./drone_mysql/data:/var/lib/mysql/:rw
      - ./drone_mysql/logs:/var/log/mysql/:rw

  # drone 服务端
  drone_server:
    image: drone/drone
    container_name: drone_server
    ports:
      - 8080:80 # drone comtainer serves via port 80, we expose to end users via port 8381
      # - 8443:443
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./drone_server/data:/data:rw
      - ./drone_server/drone:/var/lib/drone:rw
    restart: always
    environment:
      # db Config
      - DRONE_DATABASE_DATASOURCE=${MYSQL_DATABASE}:${MYSQL_PASSWORD}@tcp(drone_mysql:3306)/drone?parseTime=true #mysql配置,要与上边mysql容器中的配置一致
      - DRONE_DATABASE_DRIVER=mysql

      - DRONE_TLS_AUTOCERT=false

      # gitea Config
      # - DRONE_AGENTS_ENABLED=true
      - DRONE_GITEA_SERVER=http://${HOST}:3000 # this is internal communication with gitea server on the same network
      - DRONE_GITEA_CLIENT_ID=${DRONE_GITEA_CLIENT_ID}
      - DRONE_GITEA_CLIENT_SECRET=${DRONE_GITEA_CLIENT_SECRET}
      - DRONE_RPC_SECRET=${DRONE_RPC_SECRET} #RPC秘钥
      - DRONE_SERVER_HOST=${HOST}:8080
      - DRONE_SERVER_PROTO=http
      - DRONE_RUNNER_CAPACITY=2
      - DRONE_USER_CREATE=username:zhaobu,admin:true #管理员账号,是你想要作为管理员的Gitea用户名
      # droneclient Config

      # dronelog
      - DRONE_LOGS_PRETTY=true
      - DRONE_LOGS_COLOR=true
      - DRONE_LOGS_TEXT=true
      - DRONE_LOGS_DEBUG=true
      - DRONE_LOGS_TRACE=true
    depends_on:
      - gitea_server
    networks:
      dronenet:

  drone_agent:
    image: drone/agent:latest
    container_name: drone_agent
    restart: always
    depends_on:
      - drone_server
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - DRONE_RPC_PROTO=http
      - DRONE_RPC_HOST=drone_server
      - DRONE_RPC_SECRET=${DRONE_RPC_SECRET} #RPC秘钥,要与drone_server中的一致
      - DRONE_RUNNER_CAPACITY=3
      - DRONE_LOGS_TRACE=true
      - DRONE_LOGS_DEBUG=true
      - DRONE_UI_USERNAME=root
      - DRONE_UI_PASSWORD=root
    networks:
      dronenet:
测试持续集成

使用drone_gitea_devops中的dronetest目录在gitea新建一个仓库dronetest,然后访问http://192.168.0.90:8080/登录drone,并且激活该仓库.

  1. 进入setting,添加访问Harbor的Secrets:

docker_username: admin
docker_password: Harbor12345

  1. 修改/etc/docker/daemon.json文件内容,新增"insecure-registries": ["192.168.0.90:8180"]

devops基础架构 devops平台搭建_mysql_06

在dronetest仓库根目录有.drone.yml文件

kind: pipeline
type: docker
name: default

# Create a named volume to share this directory with all pipeline steps
volumes:
  - name: cache
    temp: {}

steps:
  # 代码分析
  - name: 静态检测
    image: aosapps/drone-sonar-plugin
    settings:
      sonar_host: http://192.168.0.90:9001
      sonar_token: 204403c2cd049c9d0a0439a5eff536027b4e89c8
      # sonar_host:
      #   from_secret: sonar_host
      # sonar_token:
      #   from_secret: sonar_token

  - name: 测试
    image: golang:alpine
    environment:
      CGO_ENABLED: 0
      GOPROXY: https://goproxy.io,direct
    commands:
      - go test
    registry: https://d8c5y6di.mirror.aliyuncs.com/
    volumes:
      - name: cache
        path: /go

  - name: 编译
    image: golang:alpine
    environment:
      CGO_ENABLED: 0
      GOPROXY: https://goproxy.io,direct
    commands:
      - go build
    registry: https://d8c5y6di.mirror.aliyuncs.com/
    volumes:
      - name: cache
        path: /go

  - name: 镜像发布
    image: plugins/docker
    environment:
      CGO_ENABLED: 0
      GOPROXY: https://goproxy.io,direct
    settings:
      use_cache: true
      repo: 192.168.0.90:8180/dronetest/testgo
      dockerfile: ./Dockerfile
      # context:
      # tags: latest
      auto_tag: true
      # auto_tag_suffix: linux-amd64
      username:
        from_secret: docker_username
      password:
        from_secret: docker_password
      registry: http://192.168.0.90:8180 # 如果使用自建的镜像仓库,例如 Harbor,这里可以通过 registry 指定
      # 启用不安全通讯,才能使用http
      insecure: true
      mirror: https://d8c5y6di.mirror.aliyuncs.com

    # 自动部署容器到服务器
  - name: 部署
    image: appleboy/drone-ssh # 用于连接服务器
    settings:
      host: 192.168.0.90
      username: root
      password: 12345
      port: 22
      # command_timeout: 1000 # ssh命令行执行超时时间,300秒
      script:
        - docker pull 192.168.0.90:8180/dronetest/testgo:latest
        - docker rm -f docker-demo || true # 这里这样是因为如果不存在docker-demo,rm会报错
        - docker run -d -p 8056:8080 --name docker-demo 192.168.0.90:8180/dronetest/testgo:latest

按照这个配置,每次推送到master分支,都会触发drone持续集成阶段。最终效果如下:

devops基础架构 devops平台搭建_nginx_07

golang静态代码检测

使用sonarqube作为代码静态质量检测工具
使用如下docker-compose启动sonarqube服务:

version: "3.7"

networks:
  sonarqubenet:
    external: false
    name: sonarqubenet

# volumes:
#   sonarqubedata:
#     name: sonarqubedata

services:
  sonarqube:
    image: sonarqube:8.1-community-beta
    container_name: sonarqube
    restart: always
    volumes:
      - ./sonarqube/conf:/opt/sonarqube/conf
      - ./sonarqube/data:/opt/sonarqube/data
      - ./sonarqube/logs:/opt/sonarqube/logs
      - ./sonarqube/extensions:/opt/sonarqube/extensions
    ports:
      - 9001:9000
    env_file: .env
    environment:
      # - sonar.jdbc.url=jdbc:postgresql://sonarqube_postgres/${POSTGRES_DB}
      # - sonar.jdbc.username=${POSTGRES_USER}
      # - sonar.jdbc.password=${POSTGRES_PASSWORD}
      - sonar.jdbc.username=sonar
      - sonar.jdbc.password=sonar
      - sonar.jdbc.url=jdbc:postgresql://sonarqube_postgres/sonar
    # command: ["--init"]
    networks:
      - sonarqubenet
    depends_on:
      - sonarqube_postgres

  # gitea 可以用postgres也可以用mysql
  sonarqube_postgres:
    image: postgres:alpine
    ports:
      - 5432:5432
    restart: always
    container_name: sonarqube_postgres
    volumes:
      - ./postgres/data:/var/lib/postgresql/data
    env_file: .env
    # environment:
    #   - POSTGRES_USER=${POSTGRES_USER}
    #   - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    #   - POSTGRES_DB=${POSTGRES_DB}
    networks:
      sonarqubenet:

的源码在

docker-compose-manager 访问:http://192.168.0.90:9001然后输入账号admin,密码admin登录,push代码后,可以看到结果

devops基础架构 devops平台搭建_nginx_08


还可以学习一下sonarqub的使用方法,修改检测的规则

参考了:

持续发布

使用drone-ssh插件发布

在.drone.yml文件中使用到了drone-ssh插件,在持续集成最后,会在192.168.0.90服务器上启动测试的容器服务,

可以访问http://192.168.0.90:8056/health,返回结果说明部署成功

devops基础架构 devops平台搭建_devops基础架构_09