手把手的建站思路和dev-ops方案
本文是总结的一个结合前后端项目分离、持续部署、网站反向代理、私有镜像管理、日志收集与过滤展示等内容的dev ops方案,诚然这个方案不会是市面上最成熟的和最好用的,不过在笔者我的实践中,这个方案已经相当可靠和全面,故而想总结分享。
反向代理
首先介绍网站的反向代理是用的traefik,这是一个现代化的反向代理和均衡负载器,针对微服务和云原生设计。我个人使用下来,跟nginx的区别,nginx更适合静态文件部署代理,traefik跟容器构建部署代理更贴近。

这里简单讲一下,trafik怎么使用,在自己服务器自己的目录中,创建一个新的docker-compose.yml文件:
yaml
version: '3.8'
networks:
dev-ops-network:
external: true
services:
traefik:
image: traefik:v2.10
container_name: traefik
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
networks:
- dev-ops-network
command:
- --api.dashboard=true
- --api.insecure=true
- --providers.docker=true
- --entrypoints.web.address=:80
- --entrypoints.web.http.redirections.entryPoint.to=websecure
- --entrypoints.web.http.redirections.entryPoint.scheme=https
- --entrypoints.websecure.address=:443
- --certificatesresolvers.myresolver.acme.dnschallenge=true
- --certificatesresolvers.myresolver.acme.dnschallenge.provider=myresolver
- [email protected]
- --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.rule=Host(`traefik.XXX.com`)"
- "traefik.http.routers.traefik.service=api@internal"
- "traefik.http.routers.traefik.entrypoints=websecure"
- "traefik.http.routers.traefik.tls.certresolver=myresolver"
这样配置之后,也顺带完成了SSL证书免费申请,这里配置的traefik.XXX.com,打开就是咱们网站的traefik的dashboard页面。traefik的特点就是针对容器进行自动发现,只要存在同一个容器的网络就会被traefik发现,并且用labels的配置,比如对外DNS host暴露XXX.XXX.com,还可以配置
scss
`(Host(`XXX.XXX0.com`) || Host(`XXX.XXX1.com`)) && PathPrefix(`/api`) && !PathPrefix(`/api/auth`)
这样就可以做到,XXX.XXX0.com、XXX.XXX1.com等多个DNS解析,前提是DNS已经在服务商平台解析好了。并且如果是前后端项目希望在同一个网址域名下的,可以用PathPrefix(
/api)
来给后端流量进入API,隔离前后端。
然后进入traefik网页,比如就是刚刚配置的traefik.XXX.com,然后进入dashboard的router页面,可以看到这个页面,和对应的信息。

traefik还可以跟cloudflare集成,享受cloudflare的代理模式和对应的SSL证书申请,简单配置如下:
yaml
version: '3.8'
networks:
dev-ops-network:
external: true
services:
traefik:
image: traefik:v2.10
container_name: traefik
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
networks:
- dev-ops-network
environment:
- [email protected]
- CLOUDFLARE_API_KEY=XXX
command:
- --api.dashboard=true
- --api.insecure=true
- --providers.docker=true
- --entrypoints.web.address=:80
- --entrypoints.web.http.redirections.entryPoint.to=websecure
- --entrypoints.web.http.redirections.entryPoint.scheme=https
- --entrypoints.websecure.address=:443
- --certificatesresolvers.cloudflare.acme.dnschallenge=true
- --certificatesresolvers.cloudflare.acme.dnschallenge.provider=cloudflare
- [email protected]
- --certificatesresolvers.cloudflare.acme.storage=/letsencrypt/acme.json
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.rule=Host(`proxy.XXX.com`)"
- "traefik.http.routers.traefik.service=api@internal"
- "traefik.http.routers.traefik.entrypoints=websecure"
- "traefik.http.routers.traefik.tls.certresolver=cloudflare"
这个配置里CLOUDFLARE_EMAIL需要填入自己cloudflare网站的邮件,和对应CLOUDFLARE_API_KEY是cloudflare账号的Global API KEY,然后就可以运行了。值得一提的是,如果用cloudflare来做SSL,可能需要在SSL/TLS 加密配置上改加密模式为完全,不然会有重定向过多的问题。
自托管Git服务、持续集成与镜像仓库
我这里使用自托管服务平台,是开源的gitea,当然用github组织、私人repo、enterprise版等也是可以的,这个自托管方案也不少。
持续集成选择的是jenkins,私有容器镜像仓库选择的是harbor,持续集成的选择大家可能都比较熟悉,选择私有容器镜像仓库,是因为可以做到一些功能,比如harbor就是一个企业级镜像仓库,可以提供镜像安全扫描、身份认证和访问权限管理、API支持和更详细的日志等等。
jenkins、gitea、harbor对应依赖的docker-compose yaml文件以及安装过程就不做多解释了,一般按照官网的引导便可用容器安装使用,对应需要给docker-compose.yaml文件加上:
yaml
labels:
- "traefik.enable=true"
- "traefik.http.routers.(对应的service).rule=Host(`XXX.XXXX.com`)"
- "traefik.http.routers.(对应的service).entrypoints=websecure"
- "traefik.http.routers.(对应的service).tls=true"
- "traefik.http.routers.(对应的service).tls.certresolver=cloudflare"
- "traefik.http.services.(对应的service).loadbalancer.server.port=(对应的端口)"

这里harbor有一个很有意思的问题,当harbor自己部署之后,自带的默认的密码无法正常登录。这里有一个文章解决这个问题,这个bug还挺容易遇到的....Harbor正确密码登录不上去
然后进入jenkins的网页之中,下载gitea和Generic Webhook Trigger的插件。然后在dashboard页面,选择new item,然后选择freestyle project,命名item name,然后进入项目的config页面,在Source Code Management上选择git,然后去gitea获得你上传repo的git地址,并且这里要获得gitea用户设置-应用中的access token,填入到jenkins项目配置的Credentials的Gitea Personal Access Token中。Triggers里选择Generic Webhook Trigger,在提示的Is triggered by HTTP requests to http://JENKINS_URL/generic-webhook-trigger/invoke
中,把http://JENKINS_URL/generic-webhook-trigger/invoke按照自己网站的url,配置到gitea库的webhook设置里,这里可以在gitea的授权标头里配置一个Bearer <your_token123>,然后在jenkins配置里的Generic Webhook Trigger的Token里填入<your_token123>,这样能直接分别让gitea的hook事件精准触发jenkins配置的job,比如前后端分离的服务,就区分开来trigger。然后在Environment配置USERNAME和PASSWORD,是harbor对应权限的username和password,这里要在jenkins里的Credentials先配置好。然后在Build Steps里,选择execute shell,里面填入:
yaml
docker login -u ${USERNAME} -p ${PASSWORD} harbor.XXX.com/XXX/XXX
docker build --platform=linux/amd64 -t harbor.XXX.com/XXX/XXX:alpha .
docker push harbor.XXX.com/XXX/XXX:alpha
以上build steps已经build好新的镜像,并且推送到对应的镜像仓库。
加上下一个build steps,这里需要ssh key需要在jenkins提前配置,这便可以直接trigger网站服务进行拉取新的镜像,这里给的命令是将业务服务器和CICD服务器分开的。如果是一个服务器完成,则不需要前面的ssh命令部分。
yaml
ssh -o StrictHostKeyChecking=no [email protected] 'cd /root/deploy/test && docker compose pull test-server && docker compose up -d test-server'
ssh -o StrictHostKeyChecking=no [email protected] 'docker image prune -a -f'
还有一个点是,触发jenkins还有一种方案是配置Jenkinsfile文件,先在jenkins网站下载pipeline
插件,在源代码库里就可以通过推送来触发web hook,当然也需要在Gitea(或者自己的Git托管仓库)里配置web hook,然后在Jenkins网站创建项目中,选择Pipeline
项目创建,然后选择Pipeline script from SCM,选择Git,填入你的Git repo url,Credentials填入对应的。
Jenkinsfile的配置可以如下:
yaml
pipeline {
agent any
environment {
DOCKER_REGISTRY = "harbor.XXX.com/XXX/XXX"
IMAGE_TAG = "alpha"
}
stages {
stage('Login to Harbor') {
steps {
script {
withCredentials([usernamePassword(credentialsId: 'harbor-credentials', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) {
sh 'docker login -u $USERNAME -p $PASSWORD $DOCKER_REGISTRY'
}
}
}
}
stage('Build Docker Image') {
steps {
sh 'docker build --platform=linux/amd64 -t ${DOCKER_REGISTRY}:${IMAGE_TAG} .'
}
}
stage('Push Docker Image') {
steps {
sh 'docker push ${DOCKER_REGISTRY}:${IMAGE_TAG}'
}
}
stage('Pull And Update') {
steps {
sh 'cd /root/deploy/test && docker compose pull test-server && docker compose up -d test-server'
sh 'docker image prune -a -f'
}
}
}
post {
always {
sh 'docker logout $DOCKER_REGISTRY'
}
}
}
日志收集
服务器的日志上,使用的是Grafana、Promtail 和 Loki,这三个依赖一同构成了一套完整的日志收集和可视化解决方案,可以类比ELK(Elasticsearch + Logstash + Kibana)。
Grafana是负责日志内容和各种数据源可视化:

Promtail是负责日志收集和推送,Promtail 负责从服务器或容器收集日志,并将日志数据发送到 Loki 进行存储。Loki 是 Grafana 对应的分布式日志聚合系统,专门用于高效存储和查询日志数据。
Grafana、Promtail 和 Loki的docker-compose 的配置如下:
yaml
version: '3.8'
networks:
dev-ops-network:
external: true
services:
loki:
image: grafana/loki:latest
container_name: loki
command: -config.file=/etc/loki/local-config.yaml
networks:
- dev-ops-network
labels:
- "traefik.enable=true"
- "traefik.http.routers.loki.rule=Host(`loki.XXX.com`)"
- "traefik.http.services.loki.loadbalancer.server.port=3100"
- "traefik.http.routers.loki.tls.certresolver=cloudflare"
restart: always
promtail:
image: grafana/promtail:latest
container_name: promtail
networks:
- dev-ops-network
volumes:
- (这里是服务器日志存放位置):/var/log
- ./promtail-config.yaml:/etc/promtail/config.yml
command: -config.file=/etc/promtail/config.yml
restart: always
grafana:
image: grafana/grafana:latest
container_name: grafana
networks:
- dev-ops-network
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana-data:/var/lib/grafana
labels:
- "traefik.enable=true"
- "traefik.http.routers.grafana.rule=Host(`grafana.XXXX.com`)"
- "traefik.http.services.grafana.loadbalancer.server.port=3000"
- "traefik.http.routers.grafana.tls.certresolver=cloudflare"
restart: always
volumes:
grafana-data:
在云服务上,在这个docker-compose.yaml文件同一级目录,需要配置loki-config.yaml
和promtail-config.yaml
,重点是promtail-config.yaml文件,clients的url需要配置为刚刚反向代理的loki的对应路径:
yaml
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: https://loki.XXXX.com/loki/api/v1/push
scrape_configs:
- job_name: XXX-logs
static_configs:
- targets:
- localhost
labels:
job: XXX-logs
__path__: /var/log/*.log
以及刚刚的docker-compose.yaml文件里的:volumes: - (这里是服务器日志存放位置):/var/log
这里的配置很关键,这里就是传到Grafana可视化loki数据源的文件内容。然后就可以去grafana网站登录之后,配置信息源,然后用logs等能力看服务器收集的日志内容了,并且服务器的db数据也可以配置grafana的数据源来进行监控和记录。
总结
这里建站对应的dev-ops的思路就是,Gitea负责Git库和推送代码的web hook触发,Jenkins负责CI/CD的触发接受以及docker build和push,Harbor负责docker镜像私有化仓库存储,并且Jenkins在镜像完成build和push之后,trigger业务服务更新新的镜像。
