Docker 快速部署 OpenVPN
一、OpenVPN 简介
VPN 直译就是虚拟专用通道,是提供给企业之间或者个人与公司之间安全数据传输的隧道,OpenVPN 无疑是 Linux 下开源 VPN 的先锋,提供了良好的性能和友好的用户 GUI。
OpenVPN 是一个基于 OpenSSL 库的应用层 VPN 实现。和传统 VPN 相比,它的优点是简单易用。
OpenVPN 允许参与建立 VPN 的单点使用共享金钥,电子证书,或者用户名/密码来进行身份验证。它大量使用了 OpenSSL 加密库中的 SSLv3 /TLSv1 协议函式库。OpenVPN 能在 Solaris、Linux、OpenBSD、FreeBSD、NetBSD、Mac OS X 与 Windows 上运行,并包含了许多安全性的功能。它并不是一个基于 Web 的 VPN 软件,也不与 IPSec 及其他 VPN 软件包兼容。
虚拟私有网络(VPN)隧道是通过 Internet 隧道技术将两个不同地理位置的网络安全的连接起来的技术。当两个网络是使用私有 IP 地址的私有局域网络时,它们之间是不能相互访问的,这时使用隧道技术就可以使得两个子网内的主机进行通讯。例如,VPN 隧道技术经常被用于大型机构中不同办公区域子网的连接。有时,使用 VPN 隧道仅仅是因为它很安全。服务提供商与公司会使用这样一种方式架设网络,他们将重要的服务器(如,数据库,VoIP,银行服务器)放置到一个子网内,仅仅让有权限的用户通过 VPN 隧道进行访问。如果需要搭建一个安全的 VPN 隧道,通常会选用 IPSec,因为 IPSec VPN 隧道被多重安全层所保护。
VPN (虚拟专用网)发展至今已经不在是一个单纯的经过加密的访问隧道了,它已经融合了访问控制、传输管理、加密、路由选择、可用性管理等多种功能,并在全球的信息安全体系中发挥着重要的作用。也在网络上,有关各种 VPN 协议优缺点的比较是仁者见仁,智者见智,很多技术人员由于出于使用目的考虑,包括访问控制、 安全和用户简单易用,灵活扩展等各方面,权衡利弊,难以取舍;尤其在 VOIP 语音环境中,网络安全显得尤为重要,因此现在越来越多的网络电话和语音网关支持 VPN 协议。
二、开始部署服务端
1. 拉取镜像
[root@vpn openvpn]# docker pull zhentianxiang/openvpn:2.4.8
2. 创建挂载目录
[root@vpn openvpn]# mkdir -pv /etc/openvpn/conf
[root@vpn openvpn]# cd /etc/openvpn/3. 生成配置文件
选择使用 OpenVPN 的 UDP 还是 TCP 取决于您的特定需求和情况。每个协议都有自己的优点和缺点,以下是一些考虑因素:
UDP:
速度快: UDP 通常比 TCP 更快,因为它不进行连接建立和错误恢复的复杂过程。这使得 UDP 成为处理实时流量(如音频和视频传输)的理想选择。
较少的开销: UDP 头部较小,因此它产生的额外开销较少,适用于网络连接较慢的情况。
适用于游戏和流媒体: 由于速度快且开销低,UDP 通常用于在线游戏、流媒体以及其他需要快速数据传输和低延迟的应用。
不稳定网络: 在不稳定的网络环境中,UDP 可能更可靠,因为它不会试图重传丢失的数据包,从而减少了延迟。
TCP:
可靠性: TCP 是一种可靠的协议,它确保数据包的有序传输和丢失包的重传。这使得它在不太可靠的网络环境中更适合,如公共 Wi-Fi 或有防火墙和代理的网络。
适用于文件传输: 由于可靠性,TCP 通常用于文件传输和下载,因为它可以确保文件完整性。
穿越防火墙: 由于通常的 HTTP 流量也使用 TCP,因此 TCP 通常可以更容易穿越防火墙和代理服务器,这使得它在某些网络环境中更可用。
保密性: 在某些情况下,TCP 可能更容易混淆和伪装,以保护数据的隐私。
总结来说,UDP 通常更适合需要高速传输和低延迟的应用,而 TCP 更适合需要可靠性和穿越防火墙的应用。选择哪种协议应取决于您的特定需求以及您所在网络环境的性质。有时,将两者结合使用也是一个可行的解决方案,以满足不同类型的流量需求。
# 47.120.62.2 是公网IP,根据实际需求切换自己的公网 IP
[root@vpn openvpn]# docker run -v $(pwd):/etc/openvpn --rm zhentianxiang/openvpn:2.4.8 ovpn_genconfig -u udp://47.120.62.2使用以上命令会生成配置文件,但是我们还要进行修改,所以我这里提供了一个可用且可靠的配置文件
# 监听地址
local 0.0.0.0
# 启用服务器模式
# mode server
# tls-server
# 协议(明确为服务端模式)
proto udp
# 端口
port 1194
# 虚拟网卡设备
dev tun0
# 证书与密钥
key /etc/openvpn/pki/private/47.120.62.2.key
ca /etc/openvpn/pki/ca.crt
cert /etc/openvpn/pki/issued/47.120.62.2.crt
dh /etc/openvpn/pki/dh.pem
tls-auth /etc/openvpn/pki/ta.key 0
key-direction 0
# 表示禁用 OpenVPN 对缓冲区大小的显式设置
sndbuf 0
rcvbuf 0
# 连接保持
persist-tun
persist-key
# 日志级别
verb 5
status /tmp/openvpn-status.log
# 运行用户权限
user nobody
group nogroup
# 客户端配置目录
client-config-dir /etc/openvpn/ccd
# 允许客户端之间互相访问
client-to-client
# 限制最大客户端数量
max-clients 10
# 保持连接时间
keepalive 10 60
# 无限重连
resolv-retry infinite
# 固定 MTU 值
tun-mtu 1500
# 修复 MSS 问题
mssfix 1450
# 允许多客户端使用相同证书
duplicate-cn
# 添加连接关闭通知
explicit-exit-notify 3
# 客户端分配 IP 的地址池
server 120.10.255.0 255.255.255.0
# 子网路由,也就是说 VPN 客户端除了可以访问到 VPN 服务端的局域网地址,还可以访问到服务端之外的局域网地址
push "route 120.10.255.0 255.255.255.0" # openvpn
push "route 172.16.11.0 255.255.255.0" # 办公室1
push "route 172.16.12.0 255.255.255.0" # 办公室2
push "route 172.16.13.0 255.255.255.0" # 办公室3
# 明确禁用 IPv6
push "block-ipv6"
# DNS 配置,此配置是向 VPN 客户端进行 DNS 推送
#push "dhcp-option DNS 172.16.11.1" # 比如办公室1的 DNS 服务器4. 生成密钥文件
[root@vpn openvpn]# docker run -v $(pwd):/etc/openvpn --rm -it zhentianxiang/openvpn:2.4.8 ovpn_initpki
init-pki complete; you may now create a CA or requests.
Your newly created PKI dir is: /etc/openvpn/pki
Using SSL: openssl OpenSSL 1.1.1g 21 Apr 2020 (Library: OpenSSL 1.1.1d 10 Sep 2019)
Enter New CA Key Passphrase: 123456789 ########################### 定义 CA 跟证书密码 ###########################
Re-Enter New CA Key Passphrase: 123456789 ############################ 定义 CA 跟证书密码 ###########################
Generating RSA private key, 2048 bit long modulus (2 primes)
...............................................+++++
...................................+++++
e is 65537 (0x010001)
Can't load /etc/openvpn/pki/.rnd into RNG
140461893008712:error:2406F079:random number generator:RAND_load_file:Cannot open file:crypto/rand/randfile.c:98:Filename=/etc/openvpn/pki/.rnd
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Common Name (eg: your user, host, or server name) [Easy-RSA CA]: ############################ 回车 ###########################
CA creation complete and you may now import and sign cert requests.
Your new CA certificate file for publishing is at:
/etc/openvpn/pki/ca.crt
Using SSL: openssl OpenSSL 1.1.1g 21 Apr 2020 (Library: OpenSSL 1.1.1d 10 Sep 2019)
Generating DH parameters, 2048 bit long safe prime, generator 2
This is going to take a long time
DH parameters of size 2048 created at /etc/openvpn/pki/dh.pem
Using SSL: openssl OpenSSL 1.1.1g 21 Apr 2020 (Library: OpenSSL 1.1.1d 10 Sep 2019)
Generating a RSA private key
.......................................................................................................................+++++
.....................................+++++
writing new private key to '/etc/openvpn/pki/private/47.120.62.2.key.XXXXoEPaiN'
-----
Using configuration from /etc/openvpn/pki/safessl-easyrsa.cnf
Enter pass phrase for /etc/openvpn/pki/private/ca.key: 123456789 ######################### 输入 CA 跟证书密码 #########################
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName :ASN.1 12:'1.1.1.1'
Certificate is to be certified until Jan 1 05:45:05 2028 GMT (1080 days)
Write out database with 1 new entries
Data Base Updated
Using SSL: openssl OpenSSL 1.1.1g 21 Apr 2020 (Library: OpenSSL 1.1.1d 10 Sep 2019)
Using configuration from /etc/openvpn/pki/safessl-easyrsa.cnf
Enter pass phrase for /etc/openvpn/pki/private/ca.key: 123456789 ######################### 输入 CA 跟证书密码 ######################
An updated CRL has been created.
CRL file: /etc/openvpn/pki/crl.pem5. 生成客户端证书
vpn-client 就是客户端的名称
如果最后再加上一个 nopass 参数,表示客户端登陆不需要密码认证
[root@vpn openvpn]# docker run -v $(pwd):/etc/openvpn --rm -it zhentianxiang/openvpn:2.4.8 easyrsa build-client-full vpn-client
Using SSL: openssl OpenSSL 1.1.1g 21 Apr 2020 (Library: OpenSSL 1.1.1d 10 Sep 2019)
Generating a RSA private key
...............................................................+++++
...............+++++
writing new private key to '/etc/openvpn/pki/private/vpn-client.key.XXXXCphCkK'
Enter PEM pass phrase: 123123 # 定义客户端证书登陆密码
Verifying - Enter PEM pass phrase: 123123 # 定义客户端证书登陆密码
-----
Using configuration from /etc/openvpn/pki/safessl-easyrsa.cnf
Enter pass phrase for /etc/openvpn/pki/private/ca.key: 123456789 # 输入 ca 证书密码
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName :ASN.1 12:'vpn-client'
Certificate is to be certified until Jan 1 02:18:50 2028 GMT (1080 days)
Write out database with 1 new entries
Data Base Updated
6. 导出客户端配置
[root@vpn openvpn]# mkdir client
[root@vpn openvpn]# docker run -v $(pwd):/etc/openvpn --rm zhentianxiang/openvpn:2.4.8 ovpn_getclient vpn-client > $(pwd)/client/vpn-client.ovpn
[root@vpn openvpn]# ls -l client/
total 8
-rw-r--r-- 1 root root 5091 Jan 16 10:21 vpn-client.ovpn
7. 启动openvpn
docker-compose 启动
[root@vpn openvpn]# cat docker-compose.yml
version: '3.8'
services:
openvpn:
image: zhentianxiang/openvpn:2.4.8
container_name: openvpn
restart: always
ports:
- "1194:1194/udp"
volumes:
- /etc/localtime:/etc/localtime:ro
- ./:/etc/openvpn
cap_add:
- NET_ADMIN
stdin_open: true
tty: true三、脚本新增删除用户
1. 新增用户
[root@k8s-master1 .openvpn]# cat add-client.sh
#!/bin/bash
# 创建客户端目录
mkdir -p client
# 提示用户输入用户名
read -p "请输入您的用户名: " NAME
# 询问是否修改端口(默认 1194)
read -p "默认端口为 1194,是否修改?(y/N): " CHANGE_PORT
if [[ "$CHANGE_PORT" =~ ^[Yy]$ ]]; then
read -p "请输入新的端口号: " NEW_PORT
else
NEW_PORT=1194
fi
# 询问是否修改协议(默认 TCP)
read -p "默认协议为 TCP,是否修改?(y/N): " CHANGE_PROTOCOL
if [[ "$CHANGE_PROTOCOL" =~ ^[Yy]$ ]]; then
read -p "请输入新的协议 (tcp/udp): " NEW_PROTOCOL
else
NEW_PROTOCOL="tcp"
fi
# 输出提示信息
echo "正在为 $NAME 构建客户端证书..."
# 构建客户端证书
docker run -v $(pwd):/etc/openvpn --rm -it zhentianxiang/openvpn:2.4.8 easyrsa build-client-full $NAME
# 输出提示信息
echo "正在为 $NAME 生成客户端配置文件..."
# 生成客户端配置文件
docker run -v $(pwd):/etc/openvpn --rm zhentianxiang/openvpn:2.4.8 ovpn_getclient $NAME > $(pwd)/client/"$NAME".ovpn
# 输出提示信息
echo "正在修改 $NAME 的客户端配置文件..."
# 修改端口号(如果有变化)
if [[ "$NEW_PORT" -ne 1194 ]]; then
sed -i "s/1194/$NEW_PORT/g" client/"$NAME".ovpn
fi
# 修改协议(如果有变化)
if [[ "$NEW_PROTOCOL" != "tcp" ]]; then
sed -i "s/tcp/$NEW_PROTOCOL/g" client/"$NAME".ovpn
fi
# 修改客户端配置文件,删除 redirect-gateway def1
sed -i "s/redirect-gateway def1//g" client/"$NAME".ovpn
# 在客户端配置文件中插入相关配置
#echo -e "\nroute 172.16.246.0 255.255.255.0 vpn_gateway" >> client/"$NAME".ovpn
#echo "route 192.168.0.0 255.255.0.0 172.16.246.171" >> client/"$NAME".ovpn
tee -a client/"$NAME".ovpn <<EOF
script-security 2
route-method exe
route-delay 2
block-ipv6
tun-mtu 1500
mssfix 1450
EOF
# 输出提示信息
echo "正在重启 OpenVPN 容器..."
# 重启 OpenVPN 容器
docker restart openvpn
docker exec -it openvpn /bin/sh -c "iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE"
# 输出完成提示信息
echo "$NAME 的客户端配置文件已成功生成并修改。"
echo "配置文件位于: $(pwd)/client/$NAME.ovpn"操作如下:
[root@k8s-master1 .openvpn]# ./add-client.sh
请输入您的用户名: 甄天祥
默认端口为 1194,是否修改?(y/N): y
请输入新的端口号: 31194
默认协议为 TCP,是否修改?(y/N): y
请输入新的协议 (tcp/udp): udp
正在为 甄天祥 构建客户端证书...
Using SSL: openssl OpenSSL 1.1.1g 21 Apr 2020 (Library: OpenSSL 1.1.1d 10 Sep 2019)
Generating a RSA private key
.......+++++
...........................................................................................+++++
writing new private key to '/etc/openvpn/pki/private/甄天祥.key.XXXXOmONFB'
Enter PEM pass phrase: ########## 这里自定义客户端登陆 VPN 时的证书密码
Verifying - Enter PEM pass phrase: ########## 这里自定义客户端登陆 VPN 时的证书密码
-----
Using configuration from /etc/openvpn/pki/safessl-easyrsa.cnf
Enter pass phrase for /etc/openvpn/pki/private/ca.key: ########## 这里填写 CA 根证书密码
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName :ASN.1 12:'\0xFFFFFFE7\0xFFFFFF94\0xFFFFFF84\0xFFFFFFE5\0xFFFFFFA4\0xFFFFFFA9\0xFFFFFFE7\0xFFFFFFA5\0xFFFFFFA5'
Certificate is to be certified until Sep 13 03:00:38 2028 GMT (1080 days)
Write out database with 1 new entries
Data Base Updated
正在为 甄天祥 生成客户端配置文件...
正在修改 甄天祥 的客户端配置文件...
script-security 2
route-method exe
route-delay 2
block-ipv6
tun-mtu 1500
mssfix 1450
正在重启 OpenVPN 容器...
openvpn
甄天祥 的客户端配置文件已成功生成并修改。
配置文件位于: /root/.openvpn/client/甄天祥.ovpn2. 删除用户
[root@k8s-master1 .openvpn]# cat del-client.sh
#!/bin/bash
# 提示用户输入要删除的用户名
read -p "请输入要删除的用户名: " NAME
## 确保用户名不为空
#if [[ -z "$NAME" ]]; then
# echo "错误: 用户名不能为空!"
# exit 1
#fi
#
## 确认用户是否存在
#if [[ ! -f "pki/issued/$NAME.crt" ]]; then
# echo "错误: 用户 $NAME 不存在或证书未签发!"
# exit 1
#fi
# 输出提示信息
echo "正在撤销 $NAME 的客户端证书..."
# 撤销客户端证书
docker run -v $(pwd):/etc/openvpn --rm -it zhentianxiang/openvpn:2.4.8 easyrsa revoke $NAME
# 重新生成 CRL(证书吊销列表)
docker run -v $(pwd):/etc/openvpn --rm -it zhentianxiang/openvpn:2.4.8 easyrsa gen-crl
# 移动最新的 CRL 到 OpenVPN 目录
mv pki/crl.pem /etc/openvpn/crl.pem
# 输出提示信息
echo "正在删除 $NAME 的证书文件..."
# 删除证书和密钥
rm -f pki/issued/"$NAME".crt
rm -f pki/private/"$NAME".key
rm -f pki/reqs/"$NAME".req
# 删除客户端配置文件
rm -f client/"$NAME".ovpn
# 重启 OpenVPN 使更改生效
echo "正在重启 OpenVPN 容器..."
docker restart openvpn
docker exec -it openvpn /bin/sh -c "iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE"
# 输出完成提示信息
echo "用户 $NAME 已成功删除,并撤销其证书访问权限。"四、关于网络环境配置
1. redirect-gateway def1
这个配置是客户端截取当前主机所有路由,并且全部转发到 openvpn 网关上,如果开启这个配置的话可能会造成本地网络环境不稳定,毕竟你的出口网关要全部走到 VPN 服务器端,这样一来就会使本地网络变得卡顿,延迟高
最后一行删除 redirect-gateway def1
[root@vpn openvpn]# vim client/vpn-client.ovpn2. 静态路由配置
1. 客户端登陆 vpn 后访问网络不通问题
客户端连接登陆后无法 ping 通局域网地址,只能 ping 通 vpn 提供的 TUN0 (docker 容器内部) 网卡的地址,解决办法如下
开启 ipv4 net 转发
[root@vpn openvpn]# sysctl -w net.ipv4.ip_forward=1动态 SNAT(源地址转换)
[root@vpn openvpn]# iptables -t nat -A POSTROUTING -s 120.10.255.0/24 -j MASQUERADE核心功能:
动态 SNAT(源地址转换)
将来自
120.10.255.0/24的所有数据包的源 IP 替换为当前主机的出口 IP(如公网 IP、或主机 IP)。外部服务器看到的流量会“伪装”成来自当前主机,而非内网设备。
典型应用场景
VPN 服务器:让客户端(如
120.10.255.0/24)通过服务器访问互联网。网关/NAT 主机:内网设备共享一个公网 IP 上网。
对比 MASQUERADE vs SNAT
实际效果示例
原始数据包:
text
源IP: 120.10.255.100 → 目标IP: 8.8.8.8经过规则后:
text
源IP: [服务器的公网IP或主机内网IP] → 目标IP: 8.8.8.8
增加一条静态路由
172.17.0.2是 openvpn 容器地址
120.10.255.0/24是 openvpn 容器内的虚拟网卡地址由于 openvpn 是在容器中运行,所以要让源 IP 也就是
120.10.255.0/24通过容器 IP172.17.0.2流出到宿主机
[root@vpn openvpn]# sudo route add -net 120.10.255.0/24 gw 172.18.0.22. openvpn 所在的宿主机无法 ping 通客户端地址解决
在容器内添加 NAT 规则
-t nat: 操作 nat 表(专门用于地址转换的 iptables 表)。-A POSTROUTING: 在 POSTROUTING 链(数据包离开主机前的最后阶段)追加规则。-o tun0: 仅匹配从tun0接口(通常是 OpenVPN 的虚拟网卡)发出的流量。-j MASQUERADE: 对匹配的流量进行源地址伪装(SNAT),即隐藏容器内原始 IP,使流量看起来像是从tun0接口的 IP 发出的。
作用:
允许容器内通过 OpenVPN 隧道(
tun0)访问外部网络时,自动修改数据包的源 IP,确保返回的流量能正确路由回容器。典型场景:容器内其他服务需要通过 OpenVPN 访问外部资源时(如代理流量),此规则确保流量能正常通过 VPN 隧道并返回。
[root@vpn openvpn]# docker exec -it openvpn /bin/sh -c "iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE"
# 验证
[root@vpn openvpn]# ping 120.10.255.10
PING 120.10.255.10 (120.10.255.10) 56(84) bytes of data.
64 bytes from 120.10.255.10: icmp_seq=1 ttl=62 time=8.58 ms
64 bytes from 120.10.255.10: icmp_seq=2 ttl=62 time=6.17 ms3. 开机自动配置 iptables 规则和静态路由
[root@vpn openvpn]# vim /usr/local/bin/openvpn-route.sh
#!/bin/bash
set -e
# 1) openvpn 使用的网段
OPENVPN_NET="120.10.255.0/24"
OPENVPN_GW=$(sudo docker inspect openvpn | jq -r '.[0].NetworkSettings.Networks[].IPAddress')
# 2) 宿主机 NAT
iptables -t nat -C POSTROUTING -s "$OPENVPN_NET" -j MASQUERADE 2>/dev/null || \
iptables -t nat -A POSTROUTING -s "$OPENVPN_NET" -j MASQUERADE
# 3) 静态路由(指向 openvpn 容器 IP 作为网关)
sudo ip route | grep -q "$NET via $OPENVPN_GW" || \
sudo route add -net $OPENVPN_NET gw $OPENVPN_GW
# 4) 容器内 NAT
docker exec openvpn /bin/sh -c \
"iptables -t nat -C POSTROUTING -o tun0 -j MASQUERADE 2>/dev/null || \
iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE"[root@vpn openvpn]# chmod +x /usr/local/bin/openvpn-route.sh[root@vpn openvpn]# vim /etc/systemd/system/openvpn-route.service
[Unit]
Description=Configure OpenVPN NAT and Routing
After=network.target docker.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/openvpn-route.sh
RemainAfterExit=true
[Install]
WantedBy=multi-user.target[root@vpn openvpn]# systemctl daemon-reexec && systemctl daemon-reload && systemctl enable openvpn-route.service --now4. vpn 服务端(局域网内)访问 vpn 客户端地址(120.10.255.0/24)
局域网内的其他机器,比如 web 机器、mysql 机器 k8s 机器等等,想要访问到远在异地已经登陆 vpn 客户端
192.168.100.10是 openvpn 所在的宿主机本机 IP
[root@k8s-app-1 ~]# ip route add 120.10.255.0/24 via 192.168.100.10
[root@mysql-server ~]# ip route add 120.10.255.0/24 via 192.168.100.10
[root@web-server ~]# ip route add 120.10.255.0/24 via 192.168.100.10
# 验证
[root@k8s-app-1 ~]# ping 120.10.255.10
PING 120.10.255.10 (120.10.255.10) 56(84) bytes of data.
64 bytes from 120.10.255.10: icmp_seq=1 ttl=62 time=8.58 ms
64 bytes from 120.10.255.10: icmp_seq=2 ttl=62 time=6.17 ms3. 配置指定客户端静态地址
1. 静态 IP 分配
为特定客户端分配固定的 IP 地址:
比如指定用户 client1 的静态地址为:120.10.255.10
# 在 ccd 目录下创建以客户端证书名命名的文件
tianxiang@tianxiang:~/docker-app/openvpn/conf/ccd$ cat client1
ifconfig-push 120.10.255.10 255.255.255.02. 推送特定路由
为特定客户端推送额外的路由:
# ccd/client1 文件内容
iroute 192.168.1.0 255.255.255.0
push "route 192.168.1.0 255.255.255.0"五、监控工具
用来监控容器是否退出,如果退出则重新启动容器
1. 告警通知脚本
[root@vpn openvpn]# mkdir monitor
[root@vpn openvpn]# vim monitor/alert.py
import smtplib
import os
import argparse
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header
def send_alert_email(subject, body):
"""发送告警邮件"""
# 从环境变量获取配置
sender_email = os.getenv("SENDER_EMAIL", "xiahediyijun@163.com")
sender_name = os.getenv("SENDER_NAME", "OpenVPN 监控告警")
receiver_email = os.getenv("RECEIVER_EMAIL", "2099637909@qq.com")
password = os.getenv("EMAIL_PASSWORD", "xxxxxxxxx")
smtp_server = os.getenv("SMTP_SERVER", "smtp.163.com")
smtp_port = int(os.getenv("SMTP_PORT", "465"))
# 构建邮件内容
msg = MIMEMultipart()
msg['From'] = Header(f"{sender_name} <{sender_email}>", 'utf-8')
msg['To'] = receiver_email
msg['Subject'] = Header(subject, 'utf-8')
msg.attach(MIMEText(body, 'plain', 'utf-8'))
try:
# 连接 SMTP 服务器并发送
with smtplib.SMTP_SSL(smtp_server, smtp_port) as server:
server.login(sender_email, password)
server.sendmail(sender_email, [receiver_email], msg.as_string())
print(f"[ALERT] 告警邮件发送成功: {subject}")
return True
except Exception as e:
print(f"[ALERT] 告警邮件发送失败: {e}")
return False
def parse_arguments():
"""解析命令行参数"""
parser = argparse.ArgumentParser(description='发送告警邮件工具')
parser.add_argument('--alert', nargs=2, metavar=('SUBJECT', 'BODY'),
required=True, help='发送告警邮件的主题和内容')
return parser.parse_args()
if __name__ == "__main__":
args = parse_arguments()
send_alert_email(args.alert[0], args.alert[1])2. 监控主程序脚本
[root@vpn openvpn]# vim monitor/monitor_openvpn.sh
#!/bin/bash
# 配置日志文件和检查间隔
LOG_FILE="/var/log/monitor_openvpn_container.log"
CHECK_INTERVAL=60
CONTAINER_NAME_PATTERN="openvpn"
PYTHON_SCRIPT="/etc/openvpn/monitor/alert.py" # 替换为真实路径
ALERT_COUNT=0 # 告警触发次数
# 通过环境变量传递密码(更安全)
export EMAIL_PASSWORD="xxxxxxxxxxxx"
# 日志记录函数
function log_message {
echo "$(date '+%Y-%m-%d %H:%M:%S'): $1" >> "$LOG_FILE"
}
# 告警发送函数
function send_alert {
local subject="$1"
local body="$2"
log_message "触发告警: $subject - $body"
python3.9 "$PYTHON_SCRIPT" --alert "$subject" "$body" >> "$LOG_FILE" 2>&1
}
# 获取容器的最后 20 行日志
function get_container_logs {
local container_name=$1
docker logs --tail 20 "$container_name"
}
# 获取容器的详细信息
function get_container_info {
local container_name=$1
CONTAINER_IMAGE=$(docker inspect --format '' "$container_name")
CONTAINER_NAME=$(docker inspect --format '' "$container_name" | sed 's/\///')
}
# 主监控循环
while true; do
# 获取容器的 ID
CONTAINER_ID=$(docker ps -qf "name=$CONTAINER_NAME_PATTERN")
if [ -z "$CONTAINER_ID" ]; then
log_message "容器不存在,尝试重启..."
# 尝试重启容器
if ! docker restart "$CONTAINER_NAME_PATTERN"; then
get_container_info "$CONTAINER_NAME_PATTERN"
LOGS=$(get_container_logs "$CONTAINER_NAME_PATTERN")
ALERT_COUNT=$((ALERT_COUNT + 1))
send_alert "OpenVPN 容器重启失败" "
时间: $(date '+%Y-%m-%d %H:%M:%S')
触发次数: $ALERT_COUNT
容器名称: $CONTAINER_NAME
容器镜像: $CONTAINER_IMAGE
容器日志:
$LOGS"
else
# 容器重启后等待 5 秒,检查容器是否成功启动
sleep 5
CONTAINER_ID=$(docker ps -qf "name=$CONTAINER_NAME_PATTERN")
if [ -z "$CONTAINER_ID" ]; then
get_container_info "$CONTAINER_NAME_PATTERN"
LOGS=$(get_container_logs "$CONTAINER_NAME_PATTERN")
ALERT_COUNT=$((ALERT_COUNT + 1))
send_alert "OpenVPN 容器重启失败" "
时间: $(date '+%Y-%m-%d %H:%M:%S')
触发次数: $ALERT_COUNT
容器名称: $CONTAINER_NAME
容器镜像: $CONTAINER_IMAGE
容器日志:
$LOGS"
else
get_container_info "$CONTAINER_NAME_PATTERN"
LOGS=$(get_container_logs "$CONTAINER_NAME_PATTERN")
ALERT_COUNT=$((ALERT_COUNT + 1))
send_alert "OpenVPN 容器重启成功" "
时间: $(date '+%Y-%m-%d %H:%M:%S')
触发次数: $ALERT_COUNT
容器名称: $CONTAINER_NAME
容器镜像: $CONTAINER_IMAGE
容器日志:
$LOGS"
fi
fi
else
# 获取容器状态信息
CONTAINER_STATUS=$(docker inspect --format '' "$CONTAINER_ID")
if [ "$CONTAINER_STATUS" == "restarting" ]; then
get_container_info "$CONTAINER_NAME_PATTERN"
LOGS=$(get_container_logs "$CONTAINER_NAME_PATTERN")
ALERT_COUNT=$((ALERT_COUNT + 1))
send_alert "OpenVPN 容器重启" "
时间: $(date '+%Y-%m-%d %H:%M:%S')
触发次数: $ALERT_COUNT
容器名称: $CONTAINER_NAME
容器镜像: $CONTAINER_IMAGE
容器日志:
$LOGS"
fi
fi
sleep $CHECK_INTERVAL
done3. 守护进程
[root@vpn openvpn]# vim /etc/systemd/system/monitor_openvpn.service
[Unit]
Description=Monitor openvpn Docker container
After=docker.service
[Service]
Type=simple
Restart=always
User=root
ExecStart=/etc/openvpn/monitor/monitor_openvpn.sh
ExecStop=
[Install]
WantedBy=default.target[root@vpn openvpn]# systemctl daemon-reload
[root@vpn openvpn]# systemctl enable monitor_openvpn.service --now
[root@vpn openvpn]# systemctl status monitor_openvpn.service
● monitor_openvpn.service - Monitor openvpn Docker container
Loaded: loaded (/etc/systemd/system/monitor_openvpn.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2024-05-02 22:57:41 CST; 7s ago
Main PID: 1373453 (monitor_openvpn)
Tasks: 2 (limit: 9374)
Memory: 1.1M
CGroup: /system.slice/monitor_openvpn.service
├─1373453 /bin/bash /etc/openvpn/monitor_openvpn.sh
└─1373484 sleep 10手动触发测试



五、客户端使用
1. Windows
下载Windows OpenVPN客户端(64位):https://openvpn.net/client-connect-vpn-for-windows/
安装客户端并打开OpenVPN GUI
将.ovpn文件拖放到OpenVPN GUI窗口中
输入用户名和密码
点击”连接”,即可开始使用OpenVPN连接
2. MacOS
下载MacOS OpenVPN客户端:https://openvpn.net/client-connect-vpn-for-mac-os/
安装客户端并打开OpenVPN Connect
将.ovpn文件拖放到OpenVPN Connect窗口中
输入用户名和密码
点击”连接”,即可开始使用OpenVPN连接
3. iOS
下载iOS OpenVPN客户端:https://apps.apple.com/us/app/openvpn-connect/id590379981
安装客户端并打开OpenVPN Connect
将.ovpn文件发送到您的iOS设备
在iOS设备上选择“打开方式”为“OpenVPN Connect”
输入用户名和密码
点击”连接”,即可开始使用OpenVPN连接
4. Linux Ubuntu 使用
安装 openvpn 命令程序
sudo apt-get install openvpn把上面生成的 ovpn 复制到 Linux 中
root@big-server:/etc/openvpn/client# ls
世纪互联.ovpn password.txt
root@big-server:/etc/openvpn/client# cat 世纪互联.ovpn
client
nobind
dev tun
remote-cert-tls server
remote 47.120.62.2 1194 udp
script-security 2
route-method exe
route-delay 2
tun-mtu 1500
mssfix 1450
# 显式禁用IPv6
pull-filter ignore "route-ipv6"
pull-filter ignore "ifconfig-ipv6"
pull-filter ignore "block-ipv6"
# 如果服务器推送了不兼容的选项,忽略它们
pull-filter ignore "dhcp-option"
<key>
-----BEGIN ENCRYPTED PRIVATE KEY-----
.......................................................
-----END ENCRYPTED PRIVATE KEY-----
</key>
<cert>
-----BEGIN CERTIFICATE-----
................................................................
-----END CERTIFICATE-----
</cert>
<ca>
-----BEGIN CERTIFICATE-----
...........................
-----END CERTIFICATE-----
</ca>
key-direction 1
<tls-auth>
#
# 2048 bit OpenVPN static key
#
-----BEGIN OpenVPN Static key V1-----
................................
-----END OpenVPN Static key V1-----
</tls-auth>启动客户端
# 创建私钥登陆密码
root@big-server:/etc/openvpn/client# echo "123456" > password.txt
# 创建 systemd 守护进程
root@big-server:/etc/openvpn/client# cat /etc/systemd/system/openvpn-client.service
[Unit]
Description=OpenVPN client service
After=network.target
[Service]
Type=simple
ExecStart=/usr/sbin/openvpn --cd /etc/openvpn/client --config /etc/openvpn/client/世纪互联.ovpn --askpass /etc/openvpn/client/password.txt --log-append /var/log/openvpn-client.log
Restart=always
[Install]
WantedBy=multi-user.target
# 启动
root@big-server:/etc/openvpn/client# systemctl enable openvpn-client.service --now查看日志和网络信息
root@big-server:/etc/openvpn/client# tail -f /var/log/openvpn-client.log
Thu Sep 25 23:26:15 2025 UDP link remote: [AF_INET]60.10.34.143:31194
Thu Sep 25 23:26:15 2025 [60.10.34.143] Peer Connection Initiated with [AF_INET]60.10.34.143:31194
Thu Sep 25 23:26:16 2025 TUN/TAP device tun0 opened
Thu Sep 25 23:26:16 2025 /sbin/ip link set dev tun0 up mtu 1500
Thu Sep 25 23:26:16 2025 /sbin/ip addr add dev tun0 local 10.100.255.10 peer 10.100.255.9
Error: Invalid prefix for given prefix length.
Thu Sep 25 23:26:18 2025 ERROR: Linux route add command failed: external program exited with error status: 2
RTNETLINK answers: File exists
Thu Sep 25 23:26:18 2025 ERROR: Linux route add command failed: external program exited with error status: 2
Thu Sep 25 23:26:18 2025 Initialization Sequence Completedtun0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST> mtu 1500
inet 10.100.255.10 netmask 255.255.255.255 destination 10.100.255.9
inet6 fe80::39a6:fd51:504:c2d8 prefixlen 64 scopeid 0x20<link>
unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 100 (未指定)
RX packets 12 bytes 2333 (2.3 KB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 19 bytes 2746 (2.7 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0测试ping
root@big-server:/etc/openvpn/client# ping 10.100.255.1
PING 10.100.255.1 (10.100.255.1) 56(84) bytes of data.
64 字节,来自 10.100.255.1: icmp_seq=1 ttl=64 时间=32.2 毫秒
64 字节,来自 10.100.255.1: icmp_seq=2 ttl=64 时间=26.5 毫秒