背景

在Nginx集群有一定的规模时,比较让人头疼的问题有2个,一是如何在不reload nginx的情况下,动态更新后端rs,减少nginx reload的性能损耗,也能更好的对接到内部的部署平台;二是如何标准化的修改nginx配置并下发,统一对配置进行管理。

目标

目标一:可以在不Reload Nginx的情况下,动态更新upstream后端rs的信息

目标二:可以通过Jenkins对Nginx的配置进行更新、下发、删除文件、证书更新和回滚操作

功能预览

修改配置并上传git

配置下发

规划说明

整体架构

简要流程:

1.Nginx配置管理

  • 管理员通过jenkins配置的job修改nginx配置并以tag方式提交到gitlab
  • 管理员通过jenkins的配置下发job,下发指定tag的配置到选定的nginx集群主机上

2.Nginx动态负载均衡,使用开源upsync模块配置

  • 管理员通过调用etcd的接口维护(增加、删除、修改)指定upstream下的rs
  • Nginx集群会动态的更新指定upstream下的rs,进行负载调度

环境规划

这里规划的系统配置和角色都是实验环境,生产环境按照实际情况拆分,升配。

IP地址

主机名

系统配置

服务角色

10.0.1.222

ops-node-01

CentOS 7.9.2009 X86_64 2c*2g

Nginx-Pord-L7集群

10.0.1.223

ops-node-02

CentOS 7.9.2009 X86_64 2c*2g

Nginx-Pord-L7集群

10.0.1.224

ops-node-03

CentOS 7.9.2009 X86_64 2c*2g

Nginx-Pord-L4集群

10.0.1.225

ops-node-04

CentOS 7.9.2009 X86_64 4c*8g

etcd/gitlab/jenkins/ansible和业务应用

数据存储目录:

  • ETCD集群12379端口实例数据存储目录:/root/docker/etcd-vloume-12379
  • ETCD集群22379端口实例数据存储目录:/root/docker/etcd-vloume-22379
  • ETCD集群32379端口实例数据存储目录:/root/docker/etcd-vloume-32379
  • Gitlab数据存储目录:/root/docker/gitlab-volume
  • Jenkins数据存储目录:/root/docker/jenkins-volume
  • Nginx数据存储目录:/root/docker/nginx-volume

注意:为了便于管理,建议将Jenkins、Gitlab、Etcd独立出来,只做运维内部使用,和线上区别开。

Gitlab规划

Gitlab用于存储Nginx的配置。规划一个Nginx分组,在Nginx分组中维护两个仓库,分别是Nginx-Prod-L4(四层负载均衡配置)和Nginx-Prod-L7(七层负载均衡配置)

实际生产中会有很多的配置仓库,建议按照环境进行区分。这里的仓库命名规范:工具名称-环境-用途

Nginx规划

Nginx用于反向代理,使用新浪开源的upsync模块,结合etcd进行动态负载均衡。规划两套集群,分别是Nginx-Prod-L4(四层负载均衡集群)和Nginx-Prod-L7(七层负载均衡集群),规范标准化配置。

Nginx-Prod-L7配置规划如下:

# 存放证书的目录
├── certs
│   ├── zhushiyang.net.key
│   └── zhushiyang.net.pem
# 存放upstream从etcd拉取rs的信息,进行持久化的目录
├── dump_upstreams
│   ├── blog_cluster_upsync.conf
│   └── web_cluster_upsync.conf
├── fastcgi.conf
├── fastcgi.conf.default
├── fastcgi_params
├── fastcgi_params.default
├── koi-utf
├── koi-win
├── mime.types
├── mime.types.default
# nginx主配置文件,已经优化
├── nginx.conf
├── nginx.conf.default
├── proxy.conf
├── scgi_params
├── scgi_params.default
# 存放upstream配置的目录
├── upstreams
│   ├── blog_cluster_upsync.conf
│   └── web_cluster_upsync.conf
├── uwsgi_params
├── uwsgi_params.default
# 存放虚拟主机配置的目录
├── vhosts
│   ├── blog.zhushiyang.net.conf
│   └── www.zhushiyang.net.conf
└── win-utf

Nginx-Prod-L4配置规划如下:

├── fastcgi.conf
├── fastcgi.conf.default
├── fastcgi_params
├── fastcgi_params.default
├── koi-utf
├── koi-win
├── mime.types
├── mime.types.default
# nginx主配置文件,已经优化
├── nginx.conf
├── nginx.conf.default
├── proxy.conf
├── scgi_params
├── scgi_params.default
# 存放upstream配置的目录
├── streams
│   └── etcd.zhushiyang.net.conf
├── uwsgi_params
├── uwsgi_params.default
└── win-utf

编译的模块:

  • 健康检查模块:https://github.com/msaf1980/nginx_upstream_check_module
  • Upsync模块:https://github.com/weibocom/nginx-upsync-module
  • 开启四层代理:--with-stream

Jenkins规划

Jenkins只用作Nginx集群的配置更新和下发。规划两个目录,分别是Nginx-Prod-L4(四层负载均衡Folder)和Nginx-Prod-L7(七层负载均衡Folder)。Folder命名规范:工具名称-环境-用途

Nginx-Prod-L7的Job布局:

  • configure_deploy:下发配置文件
  • nginx.conf:更新nginx.conf文件到gitlab
  • proxy.conf:更新proxy.conf文件到gitlab
  • upstreams:维护upstream,每个upstream是一个job,命名格式是upstream名称,不需要带.conf后缀,会把配置推送到gitlab
  • vhosts:维护虚拟主机,每个虚拟主机是一个job,命名格式是域名,不需要带.conf后缀,会把配置推送到gitlab

Nginx-Prod-L4的Job布局:

  • configure_deploy:下发配置文件
  • nginx.conf:更新nginx.conf文件到gitlab
  • streams:维护stream,每个stream是一个job,命名格式是域名,不需要带.conf后缀,会把配置推送到gitlab

Etcd规划

Etcd集群用于存储Nginx的upstream下的RS信息。在一台机器上启动三个不同端口的etcd容器,组成etcd集群(生产中要分成3台机器组建集群),使用Nginx-Prod-L4代理Etcd集群。管理员调用etcd接口动态配置Nginx-Prod-L7集群的upstream。

Ansible规划

Ansible用于下发配置、检验和重启Nginx操作。不需要太复杂,需要在jenkins容器中进行安装。主机的资产文件从宿主机进行挂载。

环境搭建

注意:所有的应用均使用docker搭建。

注意:所有的应用均使用docker搭建。

注意:所有的应用均使用docker搭建。

系统初始化

按照下面的步骤正常初始化系统即可,生产环境要按照组件类别进行系统调优。

# 1.关闭防火墙
systemctl stop firewalld
systemctl disable firewalld# 2.关闭selinux
setenforce 0
sed -i 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/selinux/config# 3.关闭postfix
systemctl stop postfix.service
systemctl disable postfix.service# 4.下载epel源并安装基础依赖包
wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
yum install -y wget vim lsof unzip net-tools ntpdate bash-completion lrzsz jq git mlocate tree# 5.设置时间同步
/usr/sbin/ntpdate time1.aliyun.com
echo '*/1 * * * * /usr/sbin/ntpdate time1.aliyun.com > /dev/null 2>&1' > /var/spool/cron/root
chmod 600 /var/spool/cron/root
systemctl restart crond
# 6.设置hosts
cat >> /etc/hosts <<EOF
10.0.1.222 ops-node-01
10.0.1.223 ops-node-02
10.0.1.224 ops-node-03
10.0.1.225 ops-node-04
EOF# 7.设置UTF-8
echo "LANG=\"en_US.UTF-8\"">/etc/locale.conf
source  /etc/locale.conf# 8.设置ulimit
cat > /etc/security/limits.conf << EOF
* soft nofile 1024000
* hard nofile 1024000
* soft nproc  1024000
* hard nproc  1024000
EOFcat > /etc/security/limits.d/20-nproc.conf << EOF
*          soft    nproc     409600
root       soft    nproc     unlimited
EOF# 9.设置终端超时,别名和PS变量
vim /etc/profile
export TMOUT=0
alias grep='grep --color=auto'
alias ll='ls -l --color=auto --time-style=long-iso'
PS1='\[\e[0;33m\]\u\[\e[0m\]@\[\e[0;32m\]\h\[\e[0m\]:\[\e[0;34m\]\w\[\e[0m\]\$ 'source /etc/profile# 10.按照环境规划设置主机名
# 在10.0.1.222上执行
hostnamectl set-hostname ops-node-01
# 在10.0.1.223上执行
hostnamectl set-hostname ops-node-02
# 在10.0.1.224上执行
hostnamectl set-hostname ops-node-03
# 在10.0.1.225上执行
hostnamectl set-hostname ops-node-04

安装docker

在所有机器上安装docker,配置阿里云镜像加速,并加入开机启动。没有限制docker版本,也没有自定义太多的docker配置,可以按照实际情况定制。

yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
sed -i 's+download.docker.com+mirrors.aliyun.com/docker-ce+' /etc/yum.repos.d/docker-ce.repo
yum install -y docker-ce
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
systemctl enable --now docker
# 配置镜像加速
cat > /etc/docker/daemon.json <<EOF
{"registry-mirrors":["https://q2gr04ke.mirror.aliyuncs.com"]
}
EOFsystemctl restart docker

安装etcd

在ops-node-04,10.0.1.225节点上安装etcd集群(使用的是不同端口的etcd实例模拟的集群)。

下面的启动命令在官方文档(Run etcd clusters inside containers | etcd)中有示例,做了一些变量和参数的调整(参数对集群进行了一些优化),如果你对etcd不太了解,可以看一下官方文档(etcd),或者看一下《云原生分布式存储基石 etcd深入解析》这本书。

# 初始化环境变量,在每个节点上都要执行(这里是一台服务器模拟集群)
# 设置三个ETCD的节点名称(这里是一台服务器模拟集群)
ETCD_NAME_1=ops-etcd-01
ETCD_NAME_2=ops-etcd-02
ETCD_NAME_3=ops-etcd-03
# 设置三个ETCD的节点IP地址(这里是一台服务器模拟集群)
ETCD_NODE_IP_1=10.0.1.225
ETCD_NODE_IP_2=10.0.1.225
ETCD_NODE_IP_3=10.0.1.225
# 设置ETCD的节点唯一ID
ETCD_CLUSTER_TOKEN=etcd-cluster
# 设置ETCD初始化集群状态
ETCD_CLUSTER_STATE=new
# 设置ETCD集群CLUSTER的地址
ETCD_CLUSTER=${ETCD_NAME_1}=http://${ETCD_NODE_IP_1}:12380,${ETCD_NAME_2}=http://${ETCD_NODE_IP_2}:22380,${ETCD_NAME_3}=http://${ETCD_NODE_IP_3}:32380
# 设置ETCD镜像仓库
ETCD_REGISTRY=quay.io/coreos/etcd
# 设置ETCD镜像版本
ETCD_VERSION=v3.4.20# 初始化第一个节点
THIS_NAME=${ETCD_NAME_1}
THIS_IP=${ETCD_NODE_IP_1}
# 设置ETCD数据存储目录
ETCD_DATA_DIR=/root/docker/etcd-vloume-12379/docker run -d --restart=always --network=host --hostname ${THIS_NAME} --name ${THIS_NAME} \
-v ${ETCD_DATA_DIR}:/etcd-data \
-v /etc/localtime:/etc/localtime:ro \
${ETCD_REGISTRY}:${ETCD_VERSION} \
/usr/local/bin/etcd \--name ${THIS_NAME} \--data-dir=/etcd-data \--initial-advertise-peer-urls http://${THIS_IP}:12380 \--listen-peer-urls http://0.0.0.0:12380 \--advertise-client-urls http://${THIS_IP}:12379 \--listen-client-urls http://0.0.0.0:12379 \--initial-cluster ${ETCD_CLUSTER} \--initial-cluster-state ${ETCD_CLUSTER_STATE} \--initial-cluster-token ${ETCD_CLUSTER_TOKEN} \--heartbeat-interval=100 \--election-timeout=500 \--auto-compaction-mode=periodic \--auto-compaction-retention=1 \--quota-backend-bytes=8589934592 \--snapshot-count=5000 \--enable-v2# 初始化第二个节点
THIS_NAME=${ETCD_NAME_2}
THIS_IP=${ETCD_NODE_IP_2}
# 设置ETCD数据存储目录
ETCD_DATA_DIR=/root/docker/etcd-vloume-22379/docker run -d --restart=always --network=host --hostname ${THIS_NAME} --name ${THIS_NAME} \
-v ${ETCD_DATA_DIR}:/etcd-data \
-v /etc/localtime:/etc/localtime:ro \
${ETCD_REGISTRY}:${ETCD_VERSION} \
/usr/local/bin/etcd \--name ${THIS_NAME} \--data-dir=/etcd-data \--initial-advertise-peer-urls http://${THIS_IP}:22380 \--listen-peer-urls http://0.0.0.0:22380 \--advertise-client-urls http://${THIS_IP}:22379 \--listen-client-urls http://0.0.0.0:22379 \--initial-cluster ${ETCD_CLUSTER} \--initial-cluster-state ${ETCD_CLUSTER_STATE} \--initial-cluster-token ${ETCD_CLUSTER_TOKEN} \--heartbeat-interval=100 \--election-timeout=500 \--auto-compaction-mode=periodic \--auto-compaction-retention=1 \--quota-backend-bytes=8589934592 \--snapshot-count=5000 \--enable-v2# 初始化第三个节点
THIS_NAME=${ETCD_NAME_3}
THIS_IP=${ETCD_NODE_IP_3}
# 设置ETCD数据存储目录
ETCD_DATA_DIR=/root/docker/etcd-vloume-32379/docker run -d --restart=always --network=host --hostname ${THIS_NAME} --name ${THIS_NAME} \
-v ${ETCD_DATA_DIR}:/etcd-data \
-v /etc/localtime:/etc/localtime:ro \
${ETCD_REGISTRY}:${ETCD_VERSION} \
/usr/local/bin/etcd \--name ${THIS_NAME} \--data-dir=/etcd-data \--initial-advertise-peer-urls http://${THIS_IP}:32380 \--listen-peer-urls http://0.0.0.0:32380 \--advertise-client-urls http://${THIS_IP}:32379 \--listen-client-urls http://0.0.0.0:32379 \--initial-cluster ${ETCD_CLUSTER} \--initial-cluster-state ${ETCD_CLUSTER_STATE} \--initial-cluster-token ${ETCD_CLUSTER_TOKEN} \--heartbeat-interval=100 \--election-timeout=500 \--auto-compaction-mode=periodic \--auto-compaction-retention=1 \--quota-backend-bytes=8589934592 \--snapshot-count=5000 \--enable-v2

安装etcd客户端,主要为了使用etcdctl客户端工具

yum install -y etcd

检查etcd集群状态

ENDPOINTS=http://10.0.1.225:12379,http://10.0.1.225:22379,http://10.0.1.225:32379
ETCDCTL_API=3 etcdctl --endpoints=${ENDPOINTS} -w table endpoint status
# 集群状态正常
+-------------------------+------------------+---------+---------+-----------+-----------+------------+
|        ENDPOINT         |        ID        | VERSION | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX |
+-------------------------+------------------+---------+---------+-----------+-----------+------------+
| http://10.0.1.225:12379 | f0507c477f30cfa3 |  3.4.20 |   20 kB |      true |         2 |          9 |
| http://10.0.1.225:22379 | 8fb5ef3dcf4f436c |  3.4.20 |   20 kB |     false |         2 |          9 |
| http://10.0.1.225:32379 | c025315846602833 |  3.4.20 |   20 kB |     false |         2 |          9 |
+-------------------------+------------------+---------+---------+-----------+-----------+------------+

安装gitlab

在ops-node-04,10.0.1.225节点上安装gitlab。在安装gitlab时遇到了几个小问题:

  • 安装后用户头像无法显示,影响体验,有强迫症的人很是抓狂(替换显示头像的url)
  • 安装后需要查看gitlab的初始密码,略麻烦(生产环境还是留给系统自动初始化比较好)(采用预配置参数解决)
  • 创建项目后,项目的url并不是域名或者ip地址,而是默认的主机名,导致无法克隆项目(采用预配置参数解决)
  • 默认启动了监控的相关组件,测试的时候也基本用不到,还占用内存(采用预配置参数解决)。

预配置参数规则:只要是gitlab.rb文件中的配置都可以追加到容器的GITLAB_OMNIBUS_CONFIG变量中。

gitlab预配置文档:GitLab Docker images | GitLab

处理了上面几个问题后,启动的命令如下

# 为了降低内存占用,关闭了所有监控组件(建议测试的时候关闭);
# 设置root密码为root1234
# 替换gravatar源,解决用户头像无法显示问题
docker run -d --restart=always --hostname ops-gitlab --name ops-gitlab \
-e GITLAB_OMNIBUS_CONFIG="external_url 'http://10.0.1.225/'; \gitlab_rails['gitlab_shell_ssh_port'] = 22; \grafana['enable'] = false; \prometheus['enable'] = false; \node_exporter['enable'] = false; \redis_exporter['enable'] = false; \postgres_exporter['enable'] = false; \gitlab_exporter['enable'] = false; \alertmanager['enable'] = false; \gitlab_rails['gravatar_plain_url'] = 'http://sdn.geekzu.org/avatar/%{hash}?s=%{size}&d=identicon';\gitlab_rails['gravatar_ssl_url'] = 'https://sdn.geekzu.org/avatar/%{hash}?s=%{size}&d=identicon'" \
-e GITLAB_ROOT_PASSWORD="root1234" \
-p 443:443 -p 80:80 -p 2222:22 \
-v /root/docker/gitlab-volume/config:/etc/gitlab \
-v /root/docker/gitlab-volume/logs:/var/log/gitlab \
-v /root/docker/gitlab-volume/data:/var/opt/gitlab \
-v /etc/localtime:/etc/localtime:ro gitlab/gitlab-ee:latest

账号:root,密码:root1234

访问http://10.0.1.225,输入账号密码,登录后进行中文设置:点击【右上角头像】-【Preferences】-【Localization】-【Chinese, Simplified - 简体中文 (95% translated)】-保存

安装jenkins

在ops-node-04,10.0.1.225节点上安装jenkins。容器启动的jenkins一般在填写初始化密码后,安装插件环节会报【离线】的错误,大部分的解决方案都是修改updates/default.json中www.google.com地址以此来跳过健康检查,或者将hudson.model.UpdateCenter.xml中的域名替换http,在或者将域名替换为国内三方源,但是尝试过所有的方案后,发现该问题并没有得到解决,这是为什么呢?

jenkins使用updates/default.json文件中的www.google.com检测主机能否联网,如果检测正常,就会进入到安装插件页面,反之就会报离线错误。而且在修改配置文件重启jenkins后,jenkins还会从指定的url重新拉取updates/default.json文件导致修改无效。所以,在容器中给www.google.com设置hosts可以解决该问题,给www.google.com解析的IP地址,发送http请求时必须可以返回2xx或者3xx,因为安全性要求,内网的主机不一定都能联网,也可以使用私有地址。

# 配置Ansible资产文件
mkdir -p /root/ansible
cat > /root/ansible/hosts <<EOF
[nginx_prod_l7]
10.0.1.222
10.0.1.223
[nginx_prod_l4]
10.0.1.224
EOF# 获取www.baidu.com的IP地址,用来给www.google.com设置hosts
ALIVE_IP=$(ping -c 1 www.baidu.com|grep PING|awk -F '[()]' '{print $2}')
docker run -d -u root --add-host www.google.com:${ALIVE_IP} --restart=always --hostname ops-jenkins --name ops-jenkins \
-e  JAVA_OPTS="-Xms1024m -Xmx2048m -XX:MaxNewSize=512m" \
-p 8080:8080 -p 50000:50000 \
-v /root/docker/jenkins-volume:/var/jenkins_home \
-v /root/ansible/hosts:/root/hosts \
-v /etc/localtime:/etc/localtime:ro jenkins/jenkins:2.346.3-alpine-jdk8# 安装基础工具
docker exec -it ops-jenkins /bin/bash
sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories && apk add --no-cache  rsync git ansible# 生成公私钥
docker exec ops-jenkins ssh-keygen -t rsa
# 配置SSH免密,因为Ansible在容器中,所以进入到容器中执行,Ansible执行需要
docker exec -it /bin/bash
# 执行如下命令,输入yes回车,输入root密码
ssh-copy-id root@10.0.1.222
ssh-copy-id root@10.0.1.223
ssh-copy-id root@10.0.1.224

查看jenkins密码

docker exec ops-jenkins cat /var/jenkins_home/secrets/initialAdminPassword
# 密码如下
f52367de922d43a79850b82973447b23

访问http://10.0.1.225:8080,输入jenkins密码

接下来安装推荐的插件,如果有安装失败的插件,重试即可。安装完成后配置jenkins管理员密码(账号:admin,密码:admin123)

进入【系统管理】-【插件管理】-【可选插件】,安装以下使用到的插件(如果没有搜索到说明已经安装了):

  • 活跃选择参数插件:Active Choices
  • 修改构建名称和描述插件:Build Name and Description Setter
  • 构建的用户变量插件:build user vars
  • 扩展选择参数插件:Extended Choice Parameter
  • Git参数插件:Git Parameter
  • GitLab插件:GitLab
  • 汉化插件:Localization: Chinese (Simplified)
  • 持久化参数插件:Persistent Parameter
  • Job修改历史插件:Job Configuration History
  • 构建后操作,根据构建结果执行shell或批处理插件:Post build task

对接Gitlab

进入【系统管理】-【系统配置】-【Git plugin】,配置如下图所示

  • Global Config user.name Value:root
  • Global Config user.email Value:root@zhushiyang.net

添加ops-gitlab凭据,

  • 范围:全局 (Jenkins, nodes, items, all child items, etc)
  • 用户名:root(gitlab root用户)
  • 密码:root1234(gitlab root用户密码)
  • ID:ops-gitlab
  • 描述:ops-gitlab

至此Jenkins安装基础环境完成。

安装openresty

在ops-node-01、ops-node-02和ops-node-03上分别构建镜像。使用openresty官方的Dockerfile构建镜像为模板进行简单调整,添加alpine的国内源,编译upsync模块和check_module模块,然后使用构建的镜像启动。

openresty官方基于alpine的Dockerfile地址:https://github.com/openresty/docker-openresty/blob/1.17.8.2-5/alpine/Dockerfile

下载Nginx的nginx_upstream_check_module和nginx-upsync-module模块

  • 健康检查模块:https://github.com/msaf1980/nginx_upstream_check_module
  • Upsync模块:https://github.com/weibocom/nginx-upsync-module

先在ops-node-01上执行

mkdir /root/openresty && cd /root/openresty
# 如果git clone失败,直接到github上下载
git clone https://github.com/msaf1980/nginx_upstream_check_module
git clone https://github.com/weibocom/nginx-upsync-module# 手动下载上传到/root/openresty目录下解压
unzip nginx_upstream_check_module-master.zip
unzip nginx-upsync-module-master.zip
mv nginx_upstream_check_module-master nginx_upstream_check_module
mv nginx-upsync-module-master nginx-upsync-module

Dockerfile文件的第50-51行添加了nginx模块,85行替换了安装源,145行打check_module补丁。创建Dockerfile,执行vim Dockerfile写入下面的内容

# Dockerfile - alpine
# https://github.com/openresty/docker-openrestyARG RESTY_IMAGE_BASE="alpine"
ARG RESTY_IMAGE_TAG="3.12"FROM ${RESTY_IMAGE_BASE}:${RESTY_IMAGE_TAG}LABEL maintainer="Evan Wies <evan@neomantra.net>"# Docker Build Arguments
ARG RESTY_IMAGE_BASE="alpine"
ARG RESTY_IMAGE_TAG="3.12"
ARG RESTY_VERSION="1.17.8.2"
ARG RESTY_OPENSSL_VERSION="1.1.1g"
ARG RESTY_OPENSSL_PATCH_VERSION="1.1.1f"
ARG RESTY_OPENSSL_URL_BASE="https://www.openssl.org/source"
ARG RESTY_PCRE_VERSION="8.44"
ARG RESTY_J="1"
ARG RESTY_CONFIG_OPTIONS="\--with-compat \--with-file-aio \--with-http_addition_module \--with-http_auth_request_module \--with-http_dav_module \--with-http_flv_module \--with-http_geoip_module=dynamic \--with-http_gunzip_module \--with-http_gzip_static_module \--with-http_image_filter_module=dynamic \--with-http_mp4_module \--with-http_random_index_module \--with-http_realip_module \--with-http_secure_link_module \--with-http_slice_module \--with-http_ssl_module \--with-http_stub_status_module \--with-http_sub_module \--with-http_v2_module \--with-http_xslt_module=dynamic \--with-ipv6 \--with-mail \--with-mail_ssl_module \--with-md5-asm \--with-pcre-jit \--with-sha1-asm \--with-stream \--with-stream_ssl_module \--with-threads \--add-module=/root/nginx_upstream_check_module \--add-module=/root/nginx-upsync-module \
"
ARG RESTY_CONFIG_OPTIONS_MORE=""
ARG RESTY_LUAJIT_OPTIONS="--with-luajit-xcflags='-DLUAJIT_NUMMODE=2 -DLUAJIT_ENABLE_LUA52COMPAT'"ARG RESTY_ADD_PACKAGE_BUILDDEPS=""
ARG RESTY_ADD_PACKAGE_RUNDEPS=""
ARG RESTY_EVAL_PRE_CONFIGURE=""
ARG RESTY_EVAL_POST_MAKE=""# These are not intended to be user-specified
ARG _RESTY_CONFIG_DEPS="--with-pcre \--with-cc-opt='-DNGX_LUA_ABORT_AT_PANIC -I/usr/local/openresty/pcre/include -I/usr/local/openresty/openssl/include' \--with-ld-opt='-L/usr/local/openresty/pcre/lib -L/usr/local/openresty/openssl/lib -Wl,-rpath,/usr/local/openresty/pcre/lib:/usr/local/openresty/openssl/lib' \
"LABEL resty_image_base="${RESTY_IMAGE_BASE}"
LABEL resty_image_tag="${RESTY_IMAGE_TAG}"
LABEL resty_version="${RESTY_VERSION}"
LABEL resty_openssl_version="${RESTY_OPENSSL_VERSION}"
LABEL resty_openssl_patch_version="${RESTY_OPENSSL_PATCH_VERSION}"
LABEL resty_openssl_url_base="${RESTY_OPENSSL_URL_BASE}"
LABEL resty_pcre_version="${RESTY_PCRE_VERSION}"
LABEL resty_config_options="${RESTY_CONFIG_OPTIONS}"
LABEL resty_config_options_more="${RESTY_CONFIG_OPTIONS_MORE}"
LABEL resty_config_deps="${_RESTY_CONFIG_DEPS}"
LABEL resty_add_package_builddeps="${RESTY_ADD_PACKAGE_BUILDDEPS}"
LABEL resty_add_package_rundeps="${RESTY_ADD_PACKAGE_RUNDEPS}"
LABEL resty_eval_pre_configure="${RESTY_EVAL_PRE_CONFIGURE}"
LABEL resty_eval_post_make="${RESTY_EVAL_POST_MAKE}"COPY ./nginx_upstream_check_module/ /root/nginx_upstream_check_module
COPY ./nginx-upsync-module/ /root/nginx-upsync-moduleRUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories \&& apk add --no-cache --virtual .build-deps \build-base \coreutils \curl \gd-dev \geoip-dev \libxslt-dev \linux-headers \make \perl-dev \readline-dev \zlib-dev \${RESTY_ADD_PACKAGE_BUILDDEPS} \&& apk add --no-cache \gd \geoip \libgcc \libxslt \zlib \${RESTY_ADD_PACKAGE_RUNDEPS} \&& cd /tmp \&& if [ -n "${RESTY_EVAL_PRE_CONFIGURE}" ]; then eval $(echo ${RESTY_EVAL_PRE_CONFIGURE}); fi \&& cd /tmp \&& curl -fSL "${RESTY_OPENSSL_URL_BASE}/openssl-${RESTY_OPENSSL_VERSION}.tar.gz" -o openssl-${RESTY_OPENSSL_VERSION}.tar.gz \&& tar xzf openssl-${RESTY_OPENSSL_VERSION}.tar.gz \&& cd openssl-${RESTY_OPENSSL_VERSION} \&& if [ $(echo ${RESTY_OPENSSL_VERSION} | cut -c 1-5) = "1.1.1" ] ; then \echo 'patching OpenSSL 1.1.1 for OpenResty' \&& curl -s https://raw.githubusercontent.com/openresty/openresty/master/patches/openssl-${RESTY_OPENSSL_PATCH_VERSION}-sess_set_get_cb_yield.patch | patch -p1 ; \fi \&& if [ $(echo ${RESTY_OPENSSL_VERSION} | cut -c 1-5) = "1.1.0" ] ; then \echo 'patching OpenSSL 1.1.0 for OpenResty' \&& curl -s https://raw.githubusercontent.com/openresty/openresty/ed328977028c3ec3033bc25873ee360056e247cd/patches/openssl-1.1.0j-parallel_build_fix.patch | patch -p1 \&& curl -s https://raw.githubusercontent.com/openresty/openresty/master/patches/openssl-${RESTY_OPENSSL_PATCH_VERSION}-sess_set_get_cb_yield.patch | patch -p1 ; \fi \&& ./config \no-threads shared zlib -g \enable-ssl3 enable-ssl3-method \--prefix=/usr/local/openresty/openssl \--libdir=lib \-Wl,-rpath,/usr/local/openresty/openssl/lib \&& make -j${RESTY_J} \&& make -j${RESTY_J} install_sw \&& cd /tmp \&& curl -fSL https://nchc.dl.sourceforge.net/project/pcre/pcre/8.44/pcre-${RESTY_PCRE_VERSION}.tar.gz -o pcre-${RESTY_PCRE_VERSION}.tar.gz \&& tar xzf pcre-${RESTY_PCRE_VERSION}.tar.gz \&& cd /tmp/pcre-${RESTY_PCRE_VERSION} \&& ./configure \--prefix=/usr/local/openresty/pcre \--disable-cpp \--enable-jit \--enable-utf \--enable-unicode-properties \&& make -j${RESTY_J} \&& make -j${RESTY_J} install \&& cd /tmp \&& curl -fSL https://openresty.org/download/openresty-${RESTY_VERSION}.tar.gz -o openresty-${RESTY_VERSION}.tar.gz \&& tar xzf openresty-${RESTY_VERSION}.tar.gz \&& cd /tmp/openresty-${RESTY_VERSION}/bundle/nginx-1.17.8 \ && patch -p1 < /root/nginx_upstream_check_module/check_1.16.1+.patch \&& cd /tmp/openresty-${RESTY_VERSION} \&& eval ./configure -j${RESTY_J} ${_RESTY_CONFIG_DEPS} ${RESTY_CONFIG_OPTIONS} ${RESTY_CONFIG_OPTIONS_MORE} ${RESTY_LUAJIT_OPTIONS} \&& make -j${RESTY_J} \&& make -j${RESTY_J} install \&& cd /tmp \&& if [ -n "${RESTY_EVAL_POST_MAKE}" ]; then eval $(echo ${RESTY_EVAL_POST_MAKE}); fi \&& rm -rf \openssl-${RESTY_OPENSSL_VERSION}.tar.gz openssl-${RESTY_OPENSSL_VERSION} \pcre-${RESTY_PCRE_VERSION}.tar.gz pcre-${RESTY_PCRE_VERSION} \openresty-${RESTY_VERSION}.tar.gz openresty-${RESTY_VERSION} \&& apk del .build-deps \&& mkdir -p /var/run/openresty \&& ln -sf /dev/stdout /usr/local/openresty/nginx/logs/access.log \&& ln -sf /dev/stderr /usr/local/openresty/nginx/logs/error.log \&& rm -rf /root/ngx_healthcheck_module# Add additional binaries into PATH for convenience
ENV PATH=$PATH:/usr/local/openresty/luajit/bin:/usr/local/openresty/nginx/sbin:/usr/local/openresty/bin# Copy nginx configuration filesCMD ["/usr/local/openresty/bin/openresty", "-g", "daemon off;"]# Use SIGQUIT instead of default SIGTERM to cleanly drain requests
# See https://github.com/openresty/docker-openresty/blob/master/README.md#tips--pitfalls
STOPSIGNAL SIGQUIT

构建镜像,生产环境需要把镜像上传到阿里云或者内部的镜像仓库中,一次构建即可。现在的环境中可以在ops-node-01节点导出镜像并拷贝到ops-node-02和ops-node-03两个节点。

docker build -t openresty:1.17.8.2-alpine-upsync_check .
# 导出镜像拷贝到ops-node-02和ops-node-03两个节点
docker save -o openresty.tar openresty:1.17.8.2-alpine-upsync_check
scp openresty.tar.gz root@10.0.1.223:/root
scp openresty.tar.gz root@10.0.1.224:/root
# 在ops-node-02和ops-node-03两个节点执行
docker load -i /root/openresty.tar

现在ops-node-01、ops-node-02和ops-node-03三个节点都生成了openresty:1.17.8.2-alpine-upsync_check镜像。

下载示例配置文件到三个节点的/root/docker/nginx-volume/conf目录下,示例文件地址:https://github.com/zhushiyang/nginx_config.git,说明见Nginx规划部分。这里的配置只是初始化的,接下来会通过Jenkins统一进行管理。

# ops-node-01、ops-node-02和ops-node-03三个节点都执行
mkdir -p /root/docker/nginx-volume/conf
cd /root/docker/nginx-volume/conf
git clone https://github.com/zhushiyang/nginx_config.git
mv nginx_config/* ./
rm -rf nginx_config
# 文件web_cluster.conf不是本次的重点,删除掉
rm -f upstreams/web_cluster.conf 

在ops-node-01、ops-node-02和ops-node-03三个节点启动openresty

docker run -d --restart=always --hostname ops-openresty --name ops-openresty --network=host \
-v /root/docker/nginx-volume/conf:/usr/local/openresty/nginx/conf \
-v /root/docker/nginx-volume/logs:/var/log/nginx \
-v /etc/localtime:/etc/localtime:ro openresty:1.17.8.2-alpine-upsync_check

启动nginx容器后,查看容器日志汇报下面的错误,可暂时先忽略,是因为etcd的地址不对导致的

2022/08/26 15:48:25 [error] 10#10: recv() failed (111: Connection refused)
2022/08/26 15:48:25 [error] 10#10: upsync_recv: recv error with upstream: "web_cluster_upsync"
2022/08/26 15:48:25 [error] 7#7: recv() failed (111: Connection refused)
2022/08/26 15:48:25 [error] 7#7: upsync_recv: recv error with upstream: "web_cluster_upsync"
2022/08/26 15:48:25 [error] 8#8: recv() failed (111: Connection refused)
2022/08/26 15:48:25 [error] 8#8: upsync_recv: recv error with upstream: "web_cluster_upsync"
2022/08/26 15:48:25 [error] 9#9: recv() failed (111: Connection refused)
2022/08/26 15:48:25 [error] 9#9: upsync_recv: recv error with upstream: "web_cluster_upsync"
2022/08/26 15:48:25 [error] 8#8: send() failed (111: Connection refused)
2022/08/26 15:48:26 [error] 7#7: recv() failed (111: Connection refused)
2022/08/26 15:48:26 [error] 7#7: upsync_recv: recv error with upstream: "web_cluster_upsync"
2022/08/26 15:48:26 [error] 10#10: recv() failed (111: Connection refused)
2022/08/26 15:48:26 [error] 10#10: upsync_recv: recv error with upstream: "web_cluster_upsync"

启动验证,curl访问后,返回nginx的状态监控数据说明是正常的

curl http://10.0.1.222/stub_status
curl http://10.0.1.223/stub_status
curl http://10.0.1.224/stub_status

环境配置

配置gitlab

创建Nginx分组

在Nginx分组下创建Nginx-Prod-L4仓库(不要勾选【使用自述文件初始化仓库】)

在Nginx分组下创建Nginx-Prod-L7仓库(不要勾选【使用自述文件初始化仓库】)

最后的仓库布局如下

将Nginx的示例文件上传到两个仓库中,因为示例配置已经在ops-node-01的/root/docker/nginx-volume/conf目录下存在,所以在ops-node-01上执行即可

mkdir /root/nginx/ && cd /root/nginx/
# 如果你修改的gitlab的密码这里将root1234替换即可
git clone http://root:root1234@10.0.1.225/nginx/nginx-prod-l7.git
git clone http://root:root1234@10.0.1.225/nginx/nginx-prod-l4.gitcp -rf /root/docker/nginx-volume/conf/* nginx-prod-l4
cp -rf /root/docker/nginx-volume/conf/* nginx-prod-l7# 设置gitlab的邮箱和用户
git config --global user.email "root@zhushiyang.net"
git config --global user.name "root"cd /root/nginx/nginx-prod-l4
git add --all
git commit -m '初始化Nginx配置'
git push -u origin mastercd /root/nginx/nginx-prod-l7
git add --all
git commit -m '初始化Nginx配置'
git push -u origin master

配置jenkins

注意:jenkins的所有的job配置文件全部已经打包好,下载文件后放到任意位置并加压到/root/docker/jenkins-volume/jobs目录下,然后重启jenkins即可,但是强烈建议手动配置一遍,增加下印象,在ops-node-04上执行。

git clone https://github.com/zhushiyang/nginx-prod-l7.git
cp -rf nginx-prod-l7 /root/docker/jenkins-volume/jobs/
docker restart jenkins

下面是手动配置的过程,建议按照自定义配置走一遍,以此来熟悉每个配置的作用。

创建Folder,名称是Nginx-Prod-L7

下面是手动配置的过程,建议按照自定义配置走一遍,以此来熟悉每个配置的作用。

创建Folder,名称是Nginx-Prod-L7

创建Folder,名称是Nginx-Prod-L4

Jenkins布局如下

进入到Nginx-Prod-L7目录下,创建如下格式的job:

  • configure_deploy:下发配置文件(job类型【自由风格的软件项目】)
  • nginx.conf:更新nginx.conf文件到gitlab(job类型【自由风格的软件项目】)
  • proxy.conf:更新proxy.conf文件到gitlab(job类型【自由风格的软件项目】)
  • upstreams:维护upstream,每个upstream是一个job,命名格式是upstream名称,不需要带.conf后缀,会把配置推送到gitlab(job类型【文件夹】)
  • vhosts:维护虚拟主机,每个虚拟主机是一个job,命名格式是域名,不需要带.conf后缀,会把配置推送到gitlab(job类型【文件夹】)

job是按照nginx的配置文件来划分的,原则是经常修改的放到jenkins上进行管理,不修改的无需配置。

Nginx-Prod-L7-nginx.conf配置

任务名称:nginx.conf

任务类型:构建自由风格的软件项目

描述:Nginx主配置文件

丢弃旧的构建:这里构建历史保留10个,可以按照实际情况修改,多点,少点都没关系。

参数化构建:试过文本参数,不过不理想,在编辑配置提交后,每次在输入框里显示都是默认配置,而我希望的是在修改配置后,可以在上一次的基础上继续进行修改。jenkins没有从workspace中读取文件显示在文本框里面的插件,后来发现【Persistent Text Parameter】插件可以满足要求,原理是从上一次构建记录中读取对应的参数到当前构建中。比如,我设置的APP_NAME参数的默认值是1,上一次我把APP_NAME的值改为了2,那么在新的构建中,APP_NAME的值就是2

参数类型:Persistent Text Parameter

Name:NGINX_PROCESS_EVENT_CONFIG

Default Value:

# nginx work进程用户
user   nobody;
# 根据cpu的核数自动生成work进程树
worker_processes   auto;
# 启用多核CPU;
worker_cpu_affinity   auto;
# work进程打开最大文件数
worker_rlimit_nofile   65536;#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;
#error_log  "pipe:rollback logs/error_log interval=1d baknum=7 maxsize=2G";pid   /var/run/nginx.pid;# event context
events {# 默认开启,解决惊群问题accept_mutex   on;# 使用epolluse   epoll;# 每个进程最大连接数(最大连接=连接数x进程数)worker_connections   20480;
}

Description:Nginx进程和事件模型配置

在添加一个Persistent Text Parameter

Name:NGINX_HTTP_CONFIG

Default Value:

# 七层负载均衡
http {# 隐藏nginx版本号server_tokens off; # 文件扩展名与文件类型映射表include   mime.types;log_format  main  '$remote_addr - $remote_user [$time_local] "$request" ''$status $body_bytes_sent "$http_referer" ''"$http_user_agent" "$http_x_forwarded_for"';#access_log  logs/access.log  main;#access_log  "pipe:rollback logs/access_log interval=1d baknum=7 maxsize=2G"  main;# 调整hash表,解决绑定域名过多问题server_names_hash_max_size  4096;server_names_hash_bucket_size   1024;variables_hash_max_size 4096;variables_hash_bucket_size 2048;# 默认文件类型default_type   text/html;# 设置编码格式utf-8charset   utf-8;# 当请求头部中有下划线时,认为请求有效underscores_in_headers   on;# 防止网络阻塞tcp_nopush   on;# 输出缓冲区tcp_nodelay   on;# 输出拆包大小output_buffers   4 32k;postpone_output   1460;# 缓冲区代理缓冲用户端请求的最大字节数client_header_buffer_size   128k;large_client_header_buffers   4 256k;# 客户端发送header超时client_body_timeout   30;# 发送到客户端超时send_timeout   30;# 注意:如果图片显示不正常把这个改成off。sendfile   on;# keepalive_timeout  60;keepalive_timeout   60;# 开启gzip压缩输出gzip   on;# 最小压缩文件大小gzip_min_length   1k;# 处理请求压缩的缓冲区数量和大小gzip_buffers   4 16k;# 设置健康检查共享内存check_shm_size 20M;  # 压缩类型,默认就已经包含textml,所以下面就不用再写了,写上去也不会有问题,但是会有一个warngzip_types   text/plain application/x-javascript text/css application/xml;# 压缩比率,1代表压缩比最小处理速度最快,9压缩比最大但处理速度最慢(传输快但比较消耗cpu)gzip_comp_level   9;# 压缩通过代理的所有文件gzip_proxied   any;# vary header支持gzip_vary   on;include   proxy.conf;# server配置server {  listen 80 backlog=65535;# listen 8080 backlog=65535;# listen 443 ssl backlog=65535;# ssl_certificate ./ssl/xxx/xxx.crt;# ssl_certificate_key ./ssl/xxx/xxx.key;root   /dev/null;server_name   _;# nginx状态信息location = /stub_status {stub_status;}# upstream健康检查信息location = /check_status {check_status;access_log off;error_log off;# allow SOME.IP.ADD.RESS;# deny all;}# upsync showlocation /upstream_list {upstream_show;}}# 虚拟主机配置目录include vhosts/*.conf;# upstreams配置目录include upstreams/*.conf;
}

Description:Nginx的http配置

源码管理:Git

Repository URL:http://10.0.1.225/nginx/nginx-prod-l7.git

Credentials:选择ops-gitlab

高级里面的Name:nginx-prod-l7(因为配置修改后,需要把修改推送到master分支,同时创建tag,jenkins的Git publish插件就是通过Git插件中的Name进行关联的)

Branches to build:*/master

构建环境:

Delete workspace before starts:在构建前清理workspace

Add timestamps to the Console Output:构建日志中显示时间戳

Set jenkins user build variables:设置jenkins的用户变量,在构建时可以通过${BUILD_USER}记录当前的构建用户

构建:执行 shell

脚本的内容比较简单,生产环境可以优化,添加报警功能等内容,简化版本的脚本如下

# 加锁,防止多人同时修改
if [ ! -f /tmp/${JOB_BASE_NAME}.lock ];thenecho "> ==========> 进行加锁 <=========="echo "" > /tmp/${JOB_BASE_NAME}.lock
elseecho "==========> 不能多人同时修改该文件,请稍后修改 <=========="exit 1
fi
echo "==========> 正在生成配置文件 <=========="
echo -e "${NGINX_PROCESS_EVENT_CONFIG}\n${NGINX_HTTP_CONFIG}" > ${JOB_BASE_NAME}
echo "==========> 正在校验文件变化 <=========="
# 时间戳,格式:年月日时分-202208100510
DATE_TIME=$(date +%Y%m%d%H%M)
# git status结果为0,说明有修改,其它结果说明没有修改
CHECK_RELT=$(git status ${JOB_BASE_NAME}|grep ${JOB_BASE_NAME}>/dev/null ;echo $?)
if [  ${CHECK_RELT} = "0" ];thengit add --allgit commit -m "Update ${JOB_BASE_NAME} version to ${JOB_BASE_NAME}-${DATE_TIME}-${BUILD_NUMBER} by ${BUILD_USER}"
elseecho "==========> 文件内容无变化 <=========="exit 1
fi

构建后操作:Git Publisher

勾选:Push Only If Build Succeeds,只有成功时才提交tag

勾选:Merge Results,合并结果

Add Tag:

  • Tag to push:${JOB_BASE_NAME}-${BUILD_USER}-${BUILD_NUMBER},设置tag格式job名称-构建用户-构建顺序,如:nginx.conf-admin-1
  • 勾选:Create new tag,每次构建都创建新的tag,便于配置的更新和回滚
  • Target remote name:nginx-prod-l7,关联指定Name为nginx-prod-l7的配置

Add Branches:

  • Branch to push:master,文件更新后同步更新的分支
  • Target remote name:nginx-prod-l7,关联指定Name为nginx-prod-l7的配置

构建后操作:Post build task,主要使用Script,执行删除锁文件

Script内容如下:

echo "==========> 删除锁文件 <=========="
rm -f /tmp/${JOB_BASE_NAME}.lock

运行该job,先按照默认参数执行,默认的参数删除了示例配置中的stream配置

然后再构建一次,在NGINX_PROCESS_EVENT_CONFIG的输入框的第一行,添加一行注释# 再次构建

查看Nginx-Prod-L7的git仓库,发现nginx.conf配置已经更新

生成的tag

然后再点击一次构建(不要执行,这一次只是为了看下参数的内容,由于我一开始调试了一次,所以我的构建数是3,你们的应该是2),上次的修改记录保存下来了

第3次构建的日志保存在/root/docker/jenkins-volume/jobs/Nginx-Prod-L7/jobs/nginx.conf/builds/3/build.xml文件中,jenkins的Persistent Text Parameter参数就是读取该文件相同变量的值。

Nginx-Pord-L7-proxy.conf配置

proxy.conf定义的是全局配置反向代理配置文件,偶尔会有调整。新建jenkins的job,因为proxy.conf和nginx.conf的配置大同小异,所以从nginx.conf进行复制

要修改的配置如下

描述:修改为Nginx的proxy.conf文件

参数:删除所有的Persistent Text Parameter,然后再添加Persistent Text Parameter,设置Name为NGINX_PROXY_CONFIG,内容如下

proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE_ADDR $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header RealIP $remote_addr;
proxy_set_header Host $host;
proxy_set_header Connection "";
proxy_set_header host-name $hostname;
proxy_pass_header Server;client_max_body_size    30m;
proxy_connect_timeout   120s;
proxy_send_timeout      120s;
proxy_read_timeout      120s;
proxy_temp_file_write_size 1024m;
proxy_buffer_size               32k;
proxy_buffers                   4 32k;
proxy_busy_buffers_size 64k;proxy_ignore_client_abort on;#失败不重试
proxy_next_upstream_tries  1;

Description:修改为Nginx的代理配置

构建:执行shell修改内容如下

if [ ! -f /tmp/${JOB_BASE_NAME}.lock ];thenecho "> ==========> 进行加锁 <=========="echo "" > /tmp/${JOB_BASE_NAME}.lock
elseecho "==========> 不能多人同时修改该文件,请稍后修改 <=========="exit 1
fi
echo "==========> 正在生成配置文件 <=========="
echo -e "${NGINX_PROXY_CONFIG}" > ${JOB_BASE_NAME}
echo "==========> 正在校验文件变化 <=========="
# 时间戳,格式:年月日时分-202208100510
DATE_TIME=$(date +%Y%m%d%H%M)
# git status结果为0,说明有修改,其它结果说明没有修改
CHECK_RELT=$(git status ${JOB_BASE_NAME}|grep ${JOB_BASE_NAME}>/dev/null ;echo $?)
if [  ${CHECK_RELT} = "0" ];thengit add --allgit commit -m "Update ${JOB_BASE_NAME} version to ${JOB_BASE_NAME}-${DATE_TIME}-${BUILD_NUMBER} by ${BUILD_USER}"
elseecho "==========> 文件内容无变化 <=========="exit 1
fi

其它的配置无需修改了,直接保存即可。点击构建,在输入框的第一行添加内容【测试proxy.conf构建】

构建成功后,查看Nginx-Pord-L7仓库的proxy.conf文件内容

生成的tag

Nginx-Prod-L7-upstreams配置

这里的配置用来管理nginx的upstream,每个去掉.conf后缀的upstream文件名作为job名称。

在Nginx-Prod-L7目录下,创建Job,类型是文件夹,名称是upstreams。构建后,最终会在upstreams目录下生成${JOB_BASE_NAME}.conf文件。

因为job的配置和其他配置大致相同,可以使用jenkins的job复制功能。jenkins的job无法跨文件夹进行复制,所以可以先把proxy.conf的job移动到upstreams文件夹下,用于复制需要。

选择移动到upstreams的目录

移动完成后,切换到upstreams目录,可以看到proxy.conf文件已经移动过来,现在新建job,名称是web_cluster_upsync,复制源是proxy.conf

要修改的内容如下

描述:修改为Nginx的upstream配置文件

构建参数:将Persistent Text Parameter的Name修改为NGINX_UPSTREAM_CONFIG,默认值如下(在使用时去掉下面的注释)

# ${JOB_BASE_NAME}对的值就是job的名称,这里是web_cluster_upsync
upstream ${JOB_BASE_NAME} {# upsync在首次配置时,必须包含一个server,不然nginx会没有server的错误server 127.0.0.1:80 weight=1 max_fails=2 fail_timeout=10;# 这里是upsync的具体配置# etcd的api:10.0.1.224:2379/v2/keys/upstreams/${JOB_BASE_NAME}# 超时时间6分钟:upsync_timeout=6m# 同步间隔500ms:upsync_interval=500ms# 使用的外部存储是etcd:upsync_type=etcd# 每次nginx启动时,是否会重新从etcd拉取数据,这里关闭:strong_dependency=offupsync 10.0.1.224:2379/v2/keys/upstreams/${JOB_BASE_NAME} upsync_timeout=6m upsync_interval=500ms upsync_type=etcd strong_dependency=off;# 用来持久化upstream的文件路径,当etcd集群异常时,会从本地文件中持久化的内容,继续提供服务upsync_dump_path /usr/local/openresty/nginx/conf/dump_upstreams/${JOB_BASE_NAME}.conf;# http_check健康检查配置check interval=3000 rise=2 fall=5 timeout=1000 type=http;check_keepalive_requests 1;check_http_send "HEAD / HTTP/1.0\r\n\r\n";check_http_expect_alive http_2xx http_3xx;
}

构建:执行shell的内容如下,内容比较简单,替换了变量,对upstreams目录是否存在进行校验

if [ ! -f /tmp/${JOB_BASE_NAME}.lock ];thenecho "> ==========> 进行加锁 <=========="echo "" > /tmp/${JOB_BASE_NAME}.lock
elseecho "==========> 不能多人同时修改该文件,请稍后修改 <=========="exit 1
fi
echo "==========> 正在生成配置文件 <=========="
[ ! -d ./upstreams ] && mkdir ./upstreams
echo "${NGINX_UPSTREAM_CONFIG}" > ./upstreams/${JOB_BASE_NAME}.conf
echo "==========> 正在校验文件变化 <=========="
# 时间戳,格式:年月日时分-202208100510
DATE_TIME=$(date +%Y%m%d%H%M)
# git status结果为0,说明有修改,其它结果说明没有修改
CHECK_RELT=$(git status ./upstreams/${JOB_BASE_NAME}.conf|grep ${JOB_BASE_NAME}.conf>/dev/null ;echo $?)
if [  ${CHECK_RELT} = "0" ];thengit add --allgit commit -m "Update ${JOB_BASE_NAME} version to ${JOB_BASE_NAME}-${DATE_TIME}-${BUILD_NUMBER} by ${BUILD_USER}"
elseecho "==========> 文件内容无变化 <=========="exit 1
fi

其他内容不变,保存即可。点击构建,在输入框的第一行添加内容【测试upstream构建】

构建成功后在git仓库Nginx-Prod-L7的upstreams目录下查看生成的web_cluster_upsync.conf配置文件,模板的变量也已经替换为期望的值

然后,把proxy.conf在移动回去

Nginx-Prod-L7-vhosts配置

这里的配置用来管理nginx的虚拟主机配置,每个去掉.conf后缀的vhosts文件名作为job名称。

在Nginx-Prod-L7目录下,创建Job,类型是文件夹,名称是vhosts。构建后,最终会在vhosts目录下生成${JOB_BASE_NAME}.conf文件。

因为job的配置和其他配置大致相同,可以使用jenkins的job复制功能。jenkins的job无法跨文件夹进行复制,所以可以先把proxy.conf的job移动到vhosts文件夹下,用于复制需要。

移动完成后,切换到vhosts目录,可以看到proxy.conf文件已经移动过来,现在新建job,名称是www.zhushiyang.net,复制源是proxy.conf

修改的内容如下

描述:修改为Nginx的vhosts文件

构建参数:将Persistent Text Parameter的Name修改为NGINX_VHOSTS_CONFIG,默认值如下(在使用时去掉下面的注释)

#server {
#    listen 80;
#    server_name ${JOB_BASE_NAME};
#    return 301 https://$server_name$request_uri;
#}server{listen 80 ;listen 443 ssl;server_name  ${JOB_BASE_NAME};access_log  /var/log/nginx/${JOB_BASE_NAME}.access.log  main;ssl_certificate      ./certs/zhushiyang.net.pem;ssl_certificate_key  ./certs/zhushiyang.net.key;ssl_session_timeout 1d;ssl_session_cache shared:MozSSL:10m;ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;ssl_prefer_server_ciphers off;ssl_protocols TLSv1.1 TLSv1.2;add_header Strict-Transport-Security "max-age=63072000" always;# www主程序location / {# 支持upsyncproxy_pass http://web_cluster_upsync;}}

构建:执行shell,修改为如下内容

if [ ! -f /tmp/${JOB_BASE_NAME}.lock ];thenecho "> ==========> 进行加锁 <=========="echo "" > /tmp/${JOB_BASE_NAME}.lock
elseecho "==========> 不能多人同时修改该文件,请稍后修改 <=========="exit 1
fi
echo "==========> 正在生成配置文件 <=========="
[ ! -d ./vhosts ] && mkdir ./upstreams
echo "${NGINX_VHOSTS_CONFIG}" > ./vhosts/${JOB_BASE_NAME}.conf
echo "==========> 正在校验文件变化 <=========="
# 时间戳,格式:年月日时分-202208100510
DATE_TIME=$(date +%Y%m%d%H%M)
# git status结果为0,说明有修改,其它结果说明没有修改
CHECK_RELT=$(git status ./vhosts/${JOB_BASE_NAME}.conf|grep ${JOB_BASE_NAME}.conf>/dev/null ;echo $?)
if [  ${CHECK_RELT} = "0" ];thengit add --allgit commit -m "Update ${JOB_BASE_NAME} version to ${JOB_BASE_NAME}-${DATE_TIME}-${BUILD_NUMBER} by ${BUILD_USER}"
elseecho "==========> 文件内容无变化 <=========="exit 1
fi

其他内容不变,保存即可。点击构建,在输入框的第一行,添加【# 测试vhosts构建】

构建成功后,在git仓库Nginx-Prod-L7的vhosts目录下查看www.zhushiyang.net.conf文件内容

然后,把proxy.conf在移动回去

Nginx-Prod-L7-configure_deploy配置

前面配的jenkins任务都是本地修改配置后推送到gitlab,并没有将nginx的配置文件分发到nginx的主机上,接下来要配置的job就是实现配置分发功能。

在Nginx-Pord-L7文件夹下新建名称为configure_deploy的自由风格软件项目。

描述:Nginx配置文件下发

丢弃旧的构建:设置为10

参数:Git 参数

名称:NGINX_GIT_TAGS

参数:Extended Choice Parameter

Name:NGINX_HOSTS

选择Basic Parameter Types

  • Parameter Type:Check Boxes
  • Number of Visible items:5
  • Delimiter:,
  • 点击Choose Source for Value

Git:

Repository URL:http://10.0.1.225/nginx/nginx-prod-l7.git

Credentials:选择添加的ops-gitlab

构建环境:

构建:执行shell,写入的内容如下

echo "==========> 正在下发配置文件 <=========="
ansible -i /root/hosts ${NGINX_HOSTS} -m synchronize -a 'src=./  dest=/root/docker/nginx-volume/conf delete=yes copy_links=yes rsync_opts=--exclude=.git'
CHECK_RELT=$(ansible -i /root/hosts ${NGINX_HOSTS} -m shell -a "docker exec ops-openresty nginx -t" > /dev/null;echo $?)
if [ ${CHECK_RELT} != "0" ];thenecho "Nginx check configuration failed!"exit 1
elseansible -i /root/hosts ${NGINX_HOSTS} -m shell -a "docker exec ops-openresty nginx -s reload"
fi

保存后,点击构建,选择一个tag和要更新nginx配置的主机

点击开始构建,查看构建日志,出现文件同步且nginx reload成功说明配置下发成功。

配置etcd

Etcd集群使用nginx的四层负载对外,在ops-node-03(10.0.1.224)上创建四层负载,监听端口是2379

mkdir /root/docker/nginx-volume/conf/streams
cat > /root/docker/nginx-volume/conf/streams/etcd.zhushiyang.net.conf <<EOF
server {listen 2379;proxy_pass etcd_cluster;
}upstream etcd_cluster {server 10.0.1.225:12379;server 10.0.1.225:22379;server 10.0.1.225:32379;
}
EOFdocker exec ops-openresty nginx -t
docker exec ops-openresty nginx -s reload

验证通过Nginx代理etcd集群情况,有返回值说明正常

ETCDCTL_API=3 etcdctl --endpoints=http://10.0.1.224:2379 -w table endpoint status
+------------------------+------------------+---------+---------+-----------+-----------+------------+
|        ENDPOINT        |        ID        | VERSION | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX |
+------------------------+------------------+---------+---------+-----------+-----------+------------+
| http://10.0.1.224:2379 | 8fb5ef3dcf4f436c |  3.4.20 |   20 kB |     false |        23 |         44 |
+------------------------+------------------+---------+---------+-----------+-----------+------------+

向ETCD集群中添加upstream名称是web_cluster_upsync的rs,rs分别是10.0.1.225:9091,10.0.1.225:9092,10.0.1.225:9093

curl -X PUT -d value="{\"weight\":1, \"max_fails\":2, \"fail_timeout\":10}" http://10.0.1.224:2379/v2/keys/upstreams/web_cluster_upsync/10.0.1.225:9091
curl -X PUT -d value="{\"weight\":1, \"max_fails\":2, \"fail_timeout\":10}" http://10.0.1.224:2379/v2/keys/upstreams/web_cluster_upsync/10.0.1.225:9092
curl -X PUT -d value="{\"weight\":1, \"max_fails\":2, \"fail_timeout\":10}" http://10.0.1.224:2379/v2/keys/upstreams/web_cluster_upsync/10.0.1.225:9093

在你的电脑上配置hosts,windows系统的hosts文件是C:\Windows\System32\drivers\etc\hosts,添加如下内容

10.0.1.223 www.zhushiyang.net

在浏览器中访问www.zhushiyang.net,你会发现报502错误

查看http健康检查页面,http://10.0.1.223/check_status,发现三个rs健康检查没有通过,因为我们还没有在10.0.1.225上面启动这三个rs

现在,在ops-node-04(10.0.1.225)上,启动三个nginx,作为域名www.zhushiyang.net的后端应用

docker run --name nginx-rs-9091 -p 9091:80 -d --restart=always nginx
docker run --name nginx-rs-9092 -p 9092:80 -d --restart=always nginx
docker run --name nginx-rs-9093 -p 9093:80 -d --restart=always nginx# 自定义nginx-rs-9091页面
docker exec -it nginx-rs-9091 /bin/bash
echo "nginx-rs-9091" > /usr/share/nginx/html/index.html# 自定义nginx-rs-9092页面
docker exec -it nginx-rs-9092 /bin/bash
echo "nginx-rs-9092" > /usr/share/nginx/html/index.html# 自定义nginx-rs-9093页面
docker exec -it nginx-rs-9093 /bin/bash
echo "nginx-rs-9093" > /usr/share/nginx/html/index.html

再次查看健康检查页面,发现三个rs健康检查都已经通过

在浏览器中访问www.zhushiyang.net,现在是正常的响应了。

然后将10.0.1.225:9091的rs从etcd中删除,在查看健康检查页面并访问www.zhushiyang.net查看负载情况

curl -X DELETE -d value="{\"weight\":1, \"max_fails\":2, \"fail_timeout\":10}" http://10.0.1.224:2379/v2/keys/upstreams/web_cluster_upsync/10.0.1.225:9091

至此,Nginx动态负载均衡和配置管理基本完成。

注意:Nginx-Pord-L4的示例这里就不在列举了,根据实际情况配置即可。

后期维护

动态负载均衡更新rs流程:在新的rs上启动服务-将rs添加到etcd集群中的指定upstream上

配置管理:

  • 新增upstream:在jenkins的upstreams目录下,复制现有的upstream,如果配置模板无变动,只需修改job名称,然后构建提交到git,在下发配置。
  • 新增虚机主机:在jenkins的vhosts目录下,复制现有的vhosts,修改job名称,修改配置,然后构建提交到git,在下发配置。
  • 更新upstream:找到要更新的upstream的job名称,构建后下发配置。
  • 更新虚拟主机:找到要更新的vhosts的job名称,构建后下发配置。

证书更新:

  • 使用jenkins的文件参数来实现,配置很容易

文件删除:

  • 使用活跃选择参数来实现,显示文件列表,通过参数传递实现文件删除

注意:

  • 生产中,下发nginx配置,不要一把梭,先灰度在生产,且生产要分批进行,并通过劫持和日志观察,判断更新的配置是否生效。
  • jenkins的参数的输入框不确定可以支持多少字节,如果nginx的虚拟主机配置几千行,能否正常生成需要验证。

etcd接口:

# 增加和修改rs
curl -X PUT -d value="{\"weight\":1, \"max_fails\":2, \"fail_timeout\":10}" http://etcd_ip:etcd_port/v2/keys/upstreams/upstream名称/rs_ip:rs_port
# 删除rs
curl -X DELETE -d value="{\"weight\":1, \"max_fails\":2, \"fail_timeout\":10}" http://etcd_ip:etcd_port/v2/keys/upstreams/upstream名称/rs_ip:rs_port

数据备份与监控:

  • Gitlab备份与监控
  • Jenkins备份备份与监控
  • Etcd备份备份与监控

监控必须告警,否则无任何意义,这些不是本次的主要内容,不做更多介绍。

学习建议

涉及到的开源组件建议至少从以下几个维度来学习和梳理:

  1. 调研的背景是什么?(冲突,瓶颈,资源)
  2. 现状是什么?(我们现在有什么?)
  3. 要达到的目标是什么?(我们需要什么?)
  4. 开源产品介绍
  5. 开源产品的理论知识
  6. 开源产品搭建(生产,测试,开发)单机,集群
  7. 如何进行优化?
  8. 如何进行监控,监控哪些指标?
  9. 哪些指标要告警?
  10. 数据备份与恢复
  11. 集群扩缩容
  12. 故障预案
  13. 搭建问题记录
  14. 版本升级

Nginx开源模块学习:

  • 健康检查模块:https://github.com/msaf1980/nginx_upstream_check_module
  • Upsync模块:https://github.com/weibocom/nginx-upsync-module

Nginx动态负载均衡与配置管理相关推荐

  1. 如何让 Nginx 动态负载均衡

    一.什么是动态负载均衡 传统的 Nginx 负载均衡,是在 Nginx config 配置文件中,对 upstream 进行修改(扩展新服务器)进行,进行负载均衡,此时就需要让 Nginx 重启加载 ...

  2. Kong动态负载均衡与服务发现

    Kong动态负载均衡 一.背景 二.通过docker 安装 Kong 三.分布式API网关存在的意义 四.Kong 的相关特性 五.Kong 体系结构 六.Kong 工作流程 七.从 nginx 配置 ...

  3. lnmp架构——nginx的负载均衡

    lnmp架构--nginx的负载均衡 1 什么是nginx 2 nginx的作用 3 nginx的特点 4 nginx的安装以及优化 4.1 安装nginx 4.2 优化 5 nginx主配置文件操作 ...

  4. Nginx之负载均衡(四)

    在上一篇博客我们介绍了 Nginx 一个很重要的功能--代理,包括正向代理和反向代理.这两个代理的核心区别是:正向代理代理的是客户端,而反向代理代理的是服务器.其中我们又重点介绍了反向代理,以及如何通 ...

  5. Nginx简介及使用Nginx实现负载均衡的原理【通俗易懂,言简意赅】

    nginx 这个轻量级.高性能的 web server 主要可以干两件事情:〉直接作为http server(代替apache,对PHP需要FastCGI处理器支持):〉另外一个功能就是作为反向代理服 ...

  6. 使用nginx+Apache负载均衡及动静分离

    使用nginx+Apache负载均衡及动静分离 介绍    LB负载均衡集群分两类: LVS (四层)和 nginx或haproxy (七层)    客户端都是通过访问分发器的VIP来访问网站 在七层 ...

  7. Nginx+Tomcat负载均衡、动静分离集群

    Nginx+Tomcat负载均衡.动静分离集群 一.Nginx实现负载均衡原理 二.Nginx 配置反向代理的主要参数 三.Nginx 动静分离实现原理和优势 1.Nginx 静态处理优势 2.动静分 ...

  8. 算法高级(14)-Nginx的负载均衡策略

    一.nginx初体验 Nginx是一个http服务器.是一个使用c语言开发的高性能的http 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器.nginx能够支撑5万并发链接,并且cp ...

  9. 【转载】Nginx简介及使用Nginx实现负载均衡的原理

    原文地址:http://blog.csdn.net/u014749862/article/details/50522276 是什么? Nginx 这个轻量级.高性能的 web server 主要可以干 ...

最新文章

  1. R语言_高级数据管理
  2. Servlet + JSP(EL表达式)
  3. 用正则表达式取出table中的所有行(支持嵌套table)
  4. 简单的无监督词统计nlp预测模型
  5. 不允许所请求的注册表访问权
  6. 什么是html的编码方式,HTML网页中“&#19978;”是什么编码方式啊?网页中&# 爱问知识人...
  7. idea实现抽象类的所有抽象方法_深入理解Java的接口和抽象类
  8. mysql增量_mysql实时增量备份
  9. mysql rr gap nextkey_mysql中的各种锁把我搞糊涂啦~
  10. (五)Netty之Selector选择器
  11. windows10 下使用Pycharm2016 基于Anaconda3 Python3.6 安装Mysql驱动总结
  12. Linux---单例模式
  13. 无setup.exe情况下安装mysql5.7.28(win10)
  14. Linux 下使用Postgre中的命令,要使用postgres这个用户
  15. C++ enum类型的一个更好的用法
  16. 软件测试中的测试文档
  17. IDEA前进后退快捷键设置
  18. win10中Elan触摸板启用“双指点击打开右键菜单“与“双指滚动“方法总结
  19. 【Android】蓝牙开发——经典蓝牙:配对与解除配对 实现配对或连接时不弹出配对框
  20. python如何爬取网站所有目录_python 爬取网站的博客目录

热门文章

  1. 空间复杂度(学习总结)
  2. c++实现二叉树中节点的最大距离 - c++语言程序开发技术文章,c++实现二叉树中节点的最大距离...
  3. SpringCache通用缓存学习
  4. 计算机网络 | NAT | 什么是NAT | 网络地址转换
  5. 关于jueqry 选择器后加 [0] 的意义 $(#formId)[0]
  6. 面试阿里不到半小时被破格录取,问我原因?还不是因为Redis
  7. 王者荣耀里面的原画和模型差距居然那么大!
  8. opencv删除小连通区域(轮廓)---iplImage版本
  9. 4【计算机编程】到底该学哪种编程语言?各种不同的编程语言现在都用来干嘛?
  10. VMware虚拟机三种网络模式:桥接模式,NAT模式,仅主机模式