1. 基于 Drone CI 服务完成其自动化部署工作流的配置和演示

一个简陋的CI/CD的主要步骤包含: clone => test => build => image => deploy => notify

1.1 依赖组件

组件名称 组件功能 访问方式
DNS 域名管理 *.michaelapps.com
Caddy 域名访问转发和HTTPS证书管理 vim /etc/caddy/Caddyfile
gitea 私有代码仓库 https://gitea.michaelapp.com/ 用户名yypan
drone CI/CD工具 https://drone.michaelapp.com/ 关联gitea账号,无需新帐号
harbor 私有docker镜像仓库 https://harbor.michaelapp.com/ 用户名admin
当前服务 webtest https://webtest.michaelapp.com/ 负载均衡/访问IP获取

1.2 caddy 组件安装

1) 安装
https://caddyserver.com/docs/install#fedora-redhat-centos

[root@master dockershell]#yum install yum-plugin-copr
[root@master dockershell]#yum copr enable @caddy/caddy
[root@master dockershell]#yum install caddy
2) 查看安装后caddy的版本
[root@master dockershell]# caddy version
v2.3.0 h1:fnrqJLa3G5vfxcxmOH/+kJOcunPLhSBnjgIvjXV/QTA=
3) 域名代理配置,配置修改 vim /etc/caddy/Caddyfile

参考文档:https://caddyserver.com/docs/caddyfile/directives/reverse_proxy

webtest.michaelapp.com {
    reverse_proxy / 127.0.0.1:9090 127.0.0.1:9091 {           #反向代理
       #lb_policy round_robin
       lb_policy ip_hash                                  #负载均衡算法
       #lb_policy least_conn
       header_up Host {http.reverse_proxy.upstream.hostport}   #真实IP
       health_path /                                           #监控检查
       health_interval 60s
       health_timeout 15s
    }
    tls panyingyun@gmail.com
}

1.3 gitea是代码仓库

参考文档:https://docs.gitea.io/zh-cn/install-with-docker/

1) gitea.sh 并且执行即可
#!/bin/bash 
mkdir -p  /home/yypan/volumes/gitea
docker stop gitea
docker rm gitea
docker run -d --name=gitea -p 10022:22 -p 13000:3000 -v /home/yypan/volumes/gitea:/data gitea/gitea:1.13.1
2)域名代理配置,配置caddy
gitea.michaelapp.com {
    log {
        output file         /home/yypan/log/gitea.log
        format single_field common_log
    }
    reverse_proxy /* 42.192.207.198:13000
    tls panyingyun@gmail.com
}
3) 访问主页进行配置

配置会自动保存配置文件为 /home/yypan/volumes/gitea/gitea/conf/app.ini,其内容如下

APP_NAME = Maxwell
RUN_MODE = prod
RUN_USER = git

[repository]
ROOT = /data/git/repositories

[repository.local]
LOCAL_COPY_PATH = /data/gitea/tmp/local-repo

[repository.upload]
TEMP_PATH = /data/gitea/uploads

[server]
APP_DATA_PATH    = /data/gitea
DOMAIN           = gitea.michaelapp.com  (*)
SSH_DOMAIN       = gitea.michaelapp.com  (*)
HTTP_PORT        = 3000
ROOT_URL         = https://gitea.michaelapp.com/ (*)
DISABLE_SSH      = false
SSH_PORT         = 10022 (*)
SSH_LISTEN_PORT  = 22
LFS_START_SERVER = true
LFS_CONTENT_PATH = /data/git/lfs
LFS_JWT_SECRET   = Hbn83oCDPUbVp_gJJcUorsg2Ha9ZFcUpPgG0xnSAS5g
OFFLINE_MODE     = false

[database]
PATH     = /data/gitea/gitea.db
DB_TYPE  = sqlite3
HOST     = localhost:3306
NAME     = gitea
USER     = root
PASSWD   = 
LOG_SQL  = false
SCHEMA   = 
SSL_MODE = disable
CHARSET  = utf8

[indexer]
ISSUE_INDEXER_PATH = /data/gitea/indexers/issues.bleve

[session]
PROVIDER_CONFIG = /data/gitea/sessions
PROVIDER        = file

[picture]
AVATAR_UPLOAD_PATH            = /data/gitea/avatars
REPOSITORY_AVATAR_UPLOAD_PATH = /data/gitea/repo-avatars
DISABLE_GRAVATAR              = false
ENABLE_FEDERATED_AVATAR       = true

[attachment]
PATH = /data/gitea/attachments

[log]
MODE                 = console
LEVEL                = info
REDIRECT_MACARON_LOG = true
MACARON              = console
ROUTER               = console
ROOT_PATH            = /data/gitea/log

[security]
INSTALL_LOCK   = true
SECRET_KEY     = TMPYQhBVvPbJ9Cbn7MkFv2QmZ9gYGyPCDS29NTIYDp8Lh0gnD9LpQQpywA6F5xj3
INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE2MTAwMTQ1MTZ9.zpKvTzqCwIBp1b7uB-tLP2PhulZyK9Fri3ioOKI5GsM

[service]
DISABLE_REGISTRATION              = false
REQUIRE_SIGNIN_VIEW               = false
REGISTER_EMAIL_CONFIRM            = false
ENABLE_NOTIFY_MAIL                = false
ALLOW_ONLY_EXTERNAL_REGISTRATION  = false
ENABLE_CAPTCHA                    = false
DEFAULT_KEEP_EMAIL_PRIVATE        = false
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
DEFAULT_ENABLE_TIMETRACKING       = true
NO_REPLY_ADDRESS                  = noreply.localhost

[oauth2]
JWT_SECRET = FLkRbfbtvCsb9qVvyGscKzQvikr00fQ5t3JwD0aWk3c

[mailer]
ENABLED = false

[openid]
ENABLE_OPENID_SIGNIN = true
ENABLE_OPENID_SIGNUP = true

1.4 镜像仓库harbor

1) 下载harbor-online-installer-v2.1.2.tgz
tar zxvf harbor-online-installer-v2.1.2.tgz
cd harbor
2) 配置harbor.yml

通过模板生成初始的harbor.yml,并且编辑harbor.yml

cp harbor.yml.tmpl harbor.yml  
vim harbor.yml

修改其中以下字段
hostname: 42.192.207.198  # 主机IP
http:
  port: 1080              # 访问端口
harbor_admin_password: xxxxxx # admin登录密码
database:                             
  password: xxxxxx            # 数据库root密码
  max_idle_conns: 50 
  max_open_conns: 1000

# The default data volume
data_volume: /home/yypan/volumes/harbor  #挂盘目录

3) 运行
sh install.sh
4)域名代理配置,配置caddy
harbor.michaelapp.com {
    log {
        output file         /home/yypan/log/harbor.log
        format single_field common_log
    }
    reverse_proxy /* 127.0.0.1:1080 {
        header_up Host {http.reverse_proxy.upstream.hostport}
        header_up X-Real-IP {http.request.remote}
        header_up X-Forwarded-Port {http.request.port}
    }
    tls panyingyun@gmail.com
}
5) 重启项目服务
	docker-compose down 
	docker-compose up -d
6) 推镜像
docker login -u admin -p [xxxxxxx] 42.192.207.198:1080
docker build -t 42.192.207.198:1080/webtest/webtest:$version .
docker push 42.192.207.198:1080/webtest/webtest:$version

1.5 Drone 安装

1) Drone Server和Runner docker-compose.yml

使用docker-compose进行安装,创建 docker-compose.yml 文件 内容如下:

version: '3'
services:
  # 容器名称
  yypan-drone-server:
    # 构建所使用的镜像
    image: drone/drone:1
    # 映射容器内80端口到宿主机的7079端口
    ports:
      - 7079:80
    # 映射容器内/data目录到宿主机的/data/drone目录
    volumes:
      - /home/yypan/volumes/drone:/data
    # 容器随docker自动启动
    restart: always
    environment:
      # Gitea 服务器地址
      - DRONE_GITEA_SERVER=https://gitea.michaelapp.com
      # Gitea OAuth2客户端ID
      - DRONE_GITEA_CLIENT_ID=4b51890e-6342-445f-8d47-aa78b11b1d31
      # Gitea OAuth2客户端密钥
      - DRONE_GITEA_CLIENT_SECRET=Lccyb22FOMnaCTqJusROQAJZMHnknhYbp9cq9FKiDmM=
      # drone的共享密钥
      - DRONE_RPC_SECRET=3db96e7d4978443106374e86ce388589
      # drone的主机名
      - DRONE_SERVER_HOST=drone.michaelapp.com
      # 外部协议方案
      - DRONE_SERVER_PROTO=https
      # 创建管理员账户,这里对应为gitea的用户名
      - DRONE_USER_CREATE=username:yypan,machine:false,admin:true,token:3db96e7d4978443106374e86ce388589


  yypan-docker-runner:
    image: drone/drone-runner-docker:1
    ports:
      - 7080:3000
    restart: always
    depends_on:
      - yypan-drone-server
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      # 用于连接到Drone服务器的协议。该值必须是http或https。
      - DRONE_RPC_PROTO=https
      # 用于连接到Drone服务器的主机名
      - DRONE_RPC_HOST=drone.michaelapp.com
      # Drone服务器进行身份验证的共享密钥,和上面设置一样
      - DRONE_RPC_SECRET=3db96e7d4978443106374e86ce388589
      # 限制运行程序可以执行的并发管道数。运行程序默认情况下执行2个并发管道。
      - DRONE_RUNNER_CAPACITY=2
      # docker runner 名称
      - DRONE_RUNNER_NAME=yypan-runner
      - DOCKER_API_VERSION=1.38
2) Drone Run
	docker-compose down
	docker-compose up -d
3) Drone 域名代理配置,配置caddy
drone.michaelapp.com {
    log {
        output file         /home/yypan/log/drone.log
        format single_field common_log
    }
    reverse_proxy /* 127.0.0.1:7079
    tls panyingyun@gmail.com
}

2 Golang 项目(Go-Web-App)为例

2.1 手动编译和运行

$ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build
$ ./webtest --port 9090

2.2 创建Drone CI/CD文件 .drone.yml

.drone.yml

kind: pipeline
name: default

steps:
  - name: Test
    image: golang
    volumes:
      - name: cache
        path: /go
    commands:
      - export GOPROXY=https://goproxy.cn,direct
      - export GO111MODULE=on
      - go test
  - name: Build and Push 
    image: docker:dind
    volumes:
      - name: dockersock
        path: /var/run/docker.sock
    commands:
      - export version="v0.23"
      - docker login -u admin -p [xxxxx] 42.192.207.198:1080
      - docker build -t 42.192.207.198:1080/webtest/webtest:$version .
      - docker push 42.192.207.198:1080/webtest/webtest:$version
  - name: Deploy
    image: docker:dind
    volumes:
      - name: dockersock
        path: /var/run/docker.sock
    commands:
      - export version="v0.23"
      - docker stop webtest9090 || true
      - docker rm webtest9090 || true
      - docker stop webtest9091 || true
      - docker rm webtest9091 || true
      - docker run -p 9090:9090 --name webtest9090 -v /home/yypan/volumes/webtest1:/data -itd harbor.michaelapp.com/webtest/webtest:$version
      - docker run -p 9091:9090 --name webtest9091 -v /home/yypan/volumes/webtest2:/data -itd harbor.michaelapp.com/webtest/webtest:$version
volumes:
  - name: cache
    host:
      path: /var/lib/cache
  - name: dockersock
    host:
      path: /var/run/docker.sock
trigger:
  branch:
    - master

2.3 webtest 域名代理配置

参考文档:https://caddyserver.com/docs/caddyfile/directives/reverse_proxy

配置修改 vim /etc/caddy/Caddyfile

webtest.michaelapp.com {
    reverse_proxy / 127.0.0.1:9090 127.0.0.1:9091 {           #反向代理
       #lb_policy round_robin
       lb_policy ip_hash                                  #负载均衡算法
       #lb_policy least_conn
       header_up Host {http.reverse_proxy.upstream.hostport}   #真实IP
       health_path /                                           #监控检查
       health_interval 60s
       health_timeout 15s
    }
    tls panyingyun@gmail.com
}

​```bash 
比如 我们修改一个版本号,并提交
git commit -m "deloy v2.3"
git push 
去drone管理后台查看具体的编译过程
https://drone.michaelapp.com/ 查看编译过程

2.4 Web访问并查看日志

docker logs -f webtest9090 | grep 183.157.56.31
docker logs -f webtest9091 | grep 183.157.56.31

观察负载均衡的过程,以及不同负载均衡算法的作用

2.5 webtest代码

//main.go 
package main

import (
	"fmt"
	"net"
	"os"
	"strings"

	"github.com/urfave/cli"
	macaron "gopkg.in/macaron.v1"
)

func Appserver(ctx *macaron.Context) string {
	return "Welcome " + ctx.RemoteAddr() + " Access Server " + getIPAddress()
}

func getIPAddress() string {
	ar := ""
	if addrs, err := net.InterfaceAddrs(); err == nil {
		for _, addr := range addrs {
			if strings.HasSuffix(addr.String(), "/24") {
				fmt.Println("network = ", addr.Network(), "addr = ", addr.String())
				ar = ar + " , " + addr.String()
			}

		}
	}
	return ar
}

func run(c *cli.Context) error {
	m := macaron.Classic()
	m.Get("/", Appserver)
	m.Run(c.String("host"), c.Int("port"))
	return nil
}

func main() {
	app := cli.NewApp()
	app.Name = "webtest"
	app.Usage = "test web and auto start server within centos7.2"
	app.Copyright = "yypan@michaelapp.com"
	app.Version = "0.0.1"
	app.Action = run
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "host",
			Value: "0.0.0.0",
		},
		cli.IntFlag{
			Name:  "port",
			Value: 9090,
		},
	}
	app.Run(os.Args)
}

2.6 更加复杂的案例

https://github.com/go-gitea/gitea/blob/main/.drone.yml

2.7 caddy操作

启动
caddy start --config /etc/caddy/Caddyfile
重新加载
caddy reload --config /etc/caddy/Caddyfile
停止
caddy stop

参考:
https://caddy.community/t/caddy-v2-how-to-proxy-websoket-v2ray-websocket-tls/7040
https://caddyserver.com/docs/caddyfile/matchers#named-matchers
https://caddyserver.com/docs/caddyfile/directives/log
https://caddyserver.com/docs/caddyfile/concepts