0%

autossh

autossh

Autossh是一个用于在不稳定网络环境下自动建立和维持SSH连接的工具。它可以监测SSH连接状态并在连接断开时自动重新连接,提供了更稳定和持续的连接。这对于需要长时间运行的SSH会话或需要持续传输数据的应用程序非常有用。

autossh的ssh的区别

Autossh是SSH的一个工具或扩展,用于在不稳定的网络环境中提供持久性和可靠性的SSH连接。因此,Autossh与SSH之间存在以下区别:

  1. 功能目的:SSH是一种网络协议,用于安全地进行远程登录和数据传输。它提供了加密和认证机制,允许用户在不安全的网络中建立安全的连接。Autossh则是在SSH基础上构建的工具,旨在提供自动重连和持久性连接功能,以应对不稳定的网络环境。
  2. 自动重连:SSH本身并不具备自动重连的功能。当SSH连接中断或网络连接不稳定时,通常需要手动重新建立连接。而Autossh则是专门设计用于自动检测连接状态并自动重新连接的工具。它能够在连接中断后自动尝试重新连接,确保持续的SSH连接。
  3. 隧道和端口转发:SSH具有隧道和端口转发功能,可以安全地转发网络流量和建立通信通道。Autossh也支持这些功能,并提供了方便的配置和管理选项,使得在不稳定网络环境中建立和维护这些隧道更加可靠和便捷。
  4. 平台和兼容性:SSH是一种网络协议,广泛支持各种操作系统和网络设备。Autossh作为一个工具,可以在多个平台上运行,包括Linux、Unix、Windows等。它是为了增强SSH连接的持久性和可靠性而开发的,不限于特定的操作系统或设备。

总之,SSH是一种网络协议,用于安全远程登录和数据传输,而Autossh是在SSH基础上提供自动重连和持久性连接功能的工具。Autossh可以在不稳定的网络环境中增强SSH连接的可靠性,并提供方便的隧道和端口转发管理选项。

1
从使用角度来讲autossh主要用于建立ssh通道,以实现内网穿透的功能。例如想要从外网可以访问到内网HTTP服务,外网服务器转发HTTP请求到内网服务器,这时就可以通过autossh建立ssh通道,然后通过http反向代理到ssh通道上,再通过ssh通道将请求转发到具体的内网端口和服务上面。但需要注意的是ssh本身就支持内网穿透以及建立ssh通道的功能,但ssh本身不支持监听通道端口以及重连功能,所以一旦出现网络异常之类的问题导致ssh通道断开,需要手动执行命令重连,这里autossh解决了ssh主要存在的问题(监听隧道端口以及自动重连)。

部署及使用

1
本身部署是极为简单的,只需要服务器端开启sshd服务,确保可以通过sshd连接,然后客户端下载autossh命令,通过autossh命令连接到服务器端即可。这里如果没有配置免密登录还需要输入密码,但一般情况需要配置免密登录(确保可以开机自启动该服务)。

autossh命令安装

1
2
3
4
5
centos:  
yum -y install epel-release #autossh在epel源里面
yum -y install autossh
ubuntu:
apt install autossh

命令使用

1
2
3
4
5
6
7
8
9
10
11
12
autossh [-V] [-M monitor_port[:echo_port]] [-f] [SSH_OPTIONS]
常用参数:
-V:打印版本号
-M:指定监视端口号,用于监测SSH连接的状态。例如:-M 20000 (0表示禁用监听端口)
-f:将Autossh放入后台运行。
-N:指定不执行远程命令。
-L:设置本地端口转发。例如:-L [本地地址:]本地端口:目标地址:目标端口。
-R:设置远程端口转发。例如:-R [远程地址:]远程端口:本地地址:本地端口。
-i:指定用于身份验证的私钥文件。
-p:指定SSH服务器的端口号。
-l:指定SSH连接的用户名。
-t:强制为SSH连接分配伪终端。
1
2
3
常用的命令格式为:autossh -M autossh监听端口(任意端口) -f -N -R A主机转发端口:B主机IP:B目的端口 root@A主机 。意为通过ssh连接到远程主机,并将其某端口转发到本地(该主机)的某端口。
例:
autossh -p 30022 -M 55557 -CNR 19999:127.0.0.1:80 root@47.108.88.23 #连接ssh服务器47.108.88.23的30022端口,并将其19999端口转发到内网的80端口。55557是autossh监控端口。

实现外网访问内网HTTP服务(通过autossh)

这里主要讲解通过使用Autossh建立持久的SSH连接,并设置端口转发功能,可以实现外网对内网的HTTP服务进行安全访问。

在服务器上实现穿透

需要两台服务器,其中的服务器端需要有固定外网ip,客户端是内网服务器,通过下面的步骤来实现内外网穿透。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
服务器端操作:
1.启动sshd服务(一般都会默认安装),systemct start sshd启动它。
安装:yum -y install sshd
2.部署并启动nginx(或httpd)。
安装: yum -y install epel-release && yum -y install nginx
3.通过配置文件反向代理到ssh通道的端口上面(这里不做解释)
客户端操作:
1.部署安装autossh命令工具
yum -y install epel-release && yum -y install autossh
2.配置免密登录服务器(可选项,但如果是容器运行则为必选项)
首先ssh-keygen命令生成密钥,并将公钥文件里面的内容(cat /root/.ssh/id_rsa.pub查看)复制到服务器端的/root/.ssh/authorized_keys中去(如果没有需要在服务器生成)
3.使用autossh命令建立ssh通道。
执行命令: autossh -M autossh监听端口(任意端口) -f -N -R A主机转发端口:B主机IP:B目的端口 root@A主机 #本机为B主机,A主机为ssh服务器端
4.启动需要穿透访问的web服务(如web服务或nginx)。

在K8S上实现穿透

这里主要实现:以云服务器K8S的ingress为入口,将ingress的域名的访问请求代理至内网K8S的ingress中,然后通过内网ingress访问到具体服务。

1
2
3
4
5
6
7
准备工作:
1.首先需要确保内网的ingress服务以及ingress反向代理规则和后端服务运行正常(保证原有服务访问没问题),并确保云服务器的ingress正常可访问,将需要穿透的对应域名解析到云服务器的ingress。(这里不会对ingress做解释,需要保证ingress本身正常)
2.云服务器的ingress需要转发到具体容器pod中,也就是这里的ssh服务器端,下面会对该容器做详细解释。这里将该容器称为autossh-server
3.内网K8S中需要运行一个容器pod,用于作为autossh命令客户端与服务器建立通道,并将所有请求转发到内网ingress中,下面会对该容器做详细解释。这里将该容器称为autossh-client
实现的架构:
云服务器INGRESS-》autossh-server-》autossh-client-》内网INGRESS
其中autossh-server除了穿透需要的sshd服务以外,还需要nginx服务作为转发。autossh-client需要autossh命令服务实现穿透,还需要nginx服务,通过nginx转发到内网ingress,所以这里实际上是走了四个nginx的转发,但前两个和后两个本质上都是在K8S内网进行的转发,开销很小。

autossh-server

因为本身该需求在网上并没有较好的已实现的容器镜像,下面的容器都是手动编写制作的。这里需要先分析容器需要的功能。

1
首先这里autossh的服务器端需要安装sshd服务和nginx服务,在运行容器时需要将nginx的配置文件目录以及sshd的密钥目录挂载出来。最后通过yaml文件部署在k8s上。
Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
FROM centos:7

ENV TZ="Asia/Shanghai"

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

RUN yum install -y epel-release kde-l10n-Chinese glibc-common
RUN yum install -y nginx crontabs
RUN echo "0 0 * * * /bin/bash /nginx_cutlog.sh" >> /var/spool/cron/root
RUN yum install -y openssh-server

RUN localedef -c -f UTF-8 -i zh_CN zh_CN.utf8
ENV LC_ALL zh_CN.utf8
RUN mkdir /var/run/sshd
RUN touch /etc/ssh/sshd_config && echo 'PermitRootLogin without-password' >> /etc/ssh/sshd_config && echo 'PubkeyAuthentication yes' >> /etc/ssh/sshd_config #禁止root密码登录且启用密钥登录
RUN ssh-keygen -t ecdsa -P '' -f /etc/ssh/ssh_host_ecdsa_key && ssh-keygen -t ed25519 -P '' -f /etc/ssh/ssh_host_ed25519_key && ssh-keygen -t rsa -P '' -f /etc/ssh/ssh_host_rsa_key #这三个是sshd默认的密钥传输使用的密钥文件

COPY startup.sh /home
COPY nginx_cutlog.sh /

EXPOSE 22

EXPOSE 80

CMD ["sh","/home/startup.sh"]
startup.sh

启动脚本

1
2
3
4
5
#!/bin/bash
echo '启动nginx...' && nginx
echo '启动crond...' && crond
echo '启动sshd...' && /usr/sbin/sshd -D
#这里启动了nginx、crontab、sshd,其中sshd以前台方式运行,因为docker运行必须要一个前台主进程。这里crontab主要是每日定时任务,执行脚本nginx_cutlog.sh,每日做日志切割以及删除一个月以前的日志文件。
nginx_cutlog.sh

改脚本通过cron每日执行,其作用为按天来切割nginx日志文件以及清除一个月之前的日志文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/bin/bash
touch /tmp/1

log_dir="/var/log/nginx"
current_date=$(date "+%Y-%m-%d")
month_ago=$(date -d "-1 month" "+%Y-%m-%d")

# 遍历目录下以.log结尾的文件
for file in "$log_dir"/*.log; do
if [[ -f "$file" ]]; then
# 获取文件的基本名称(不包含路径)
base_name=$(basename "$file")

# 检查文件名中是否已经包含日期
if [[ ! "$base_name" =~ [0-9]{4}-[0-9]{2}-[0-9]{2} ]]; then
# 构造新的文件名,将日期追加到文件名中
new_file="${log_dir}/${base_name%.*}-$current_date.${base_name##*.}"

# 切割文件
mv "$file" "$new_file"

# 创建新的空日志文件
touch "$file"
fi
fi
done

# 遍历目录下以.log结尾的文件,并删除一个月以前的日志文件
find /var/log/nginx/ -mtime +30 -name "*.log" -exec rm -rf {} \;
autossh-server.yaml

部署文件,这里挂载了nginx配置文件目录,root目录(主要是.ssh存放公钥秘钥),nginx日志目录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
apiVersion: apps/v1      #指定api版本标签
kind: StatefulSet #定义资源的类型/角色,ReplicaSet为控制器
metadata: #定义资源的元数据信息
name: autossh-server #定义资源的名称,在同一个namespace空间中必须是唯一的
labels: #定义资源标签
app: autossh-server
spec:
serviceName: autossh-server
replicas: 1 #定义副本数量
selector: #定义选择器
matchLabels: #匹配上面的标签
tier: autossh-server #匹配模板名称
template: #定义模板
metadata:
labels:
tier: autossh-server
spec:
imagePullSecrets:
- name: harborsecretkey
containers: #定义容器信息
- name: autossh #容器名,与标签名要相同
imagePullPolicy: Always
image: registry.cn-chengdu.aliyuncs.com/finsiot/centos-nginx:v1.0 #容器使用的镜像以及版本
ports:
- containerPort: 80
- containerPort: 22
volumeMounts:
- mountPath: /etc/nginx/conf.d/
name: autossh-server
subPath: conf.d
- mountPath: /root/
name: autossh-server
subPath: root
- mountPath: /var/log/nginx
name: autossh-server
subPath: log
volumes:
- name: autossh-server
persistentVolumeClaim:
claimName: autossh-nginx-conf
---
apiVersion: v1
kind: Service
metadata:
name: autossh
spec:
type: NodePort
selector:
tier: autossh-server
ports:
- name: http #端口一
protocol: TCP
port: 80
targetPort: 80
nodePort: 30080
- name: ssh
protocol: TCP #端口二
port: 22
targetPort: 22
nodePort: 30022
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: autossh-nginx-conf
namespace: master
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
1
2
镜像构建:
这里直接使用dockerbuild构建并push推送: docker build . -f Dockerfile -t docker-regisry.finsiot.com/finsiot/autossh-server:v1.0 && docker push docker-regisry.finsiot.com/finsiot/autossh-server:v1.0

autossh-client

1
client端这里首先需要autossh命令,然后还需要nginx服务,通过nginx来转发到k8s的ingress。
Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
FROM centos:7

USER root

ENV TZ="Asia/Shanghai"

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

RUN yum install -y epel-release kde-l10n-Chinese glibc-common

RUN yum install -y nginx crontabs

RUN echo "0 0 * * * /bin/bash /nginx_cutlog.sh" >> /var/spool/cron/root

RUN yum install -y autossh net-tools

RUN localedef -c -f UTF-8 -i zh_CN zh_CN.utf8
ENV LC_ALL zh_CN.utf8

COPY startup.sh /home

COPY nginx_cutlog.sh /

CMD ["sh","/home/startup.sh"]
startup.sh

启动脚本

1
2
3
4
脚本说明:
1.启动脚本里面有四个必传变量$SERVER_SSH_PORT、$CLIENT_PORT、$CLIENT_PROXY_PORT、$SSH_SERVER,具体可以看脚本echo的注释,这里如果不传将无法正常启动。
2.脚本启动会生成秘钥文件id_rsa和其对应公钥文件id_rsa.pub,请将id_rsa.pub的公钥复制到对端服务器的authorized_keys文件中,以实现免密登录,这里规定了程序如果无法免密登录的话是无法正常运行的,因为容器启动没办法交互登录ssh。
3.请将/root或者/root/.ssh目录做持久化挂载,否则每次生成的秘钥都会改变。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/bin/bash
if [[ -z "$SERVER_SSH_PORT" && -z "$CLIENT_PORT" && -z "$CLIENT_PROXY_PORT" && -z "$SSH_SERVER" ]]; then
echo "未设置 \$CLIENT_PORT 或\$CLIENT_PROXY_PORT 或 \$SSH_SERVER"
echo '$SERVER_SSH_PORT表示服务器端ssh的端口(端口号的具体数字,例如22)'
echo '$CLIENT_PORT表示本地客户端的autossh监控端口(端口号的具体数字,例如22222)'
echo '$CLIENT_PROXY_PORT表示远程端口转发到本地的ip及端口(例如8888:127.0.0.1:80,表示服务端的8888转发到本地80)'
echo '$SSH_SERVER表示服务器端的用户名和IP地址(例如root@47.108.88.235)'
exit 1
fi

#判断keygen是否存在,不存在自动创建。

if [ ! -d "/root/.ssh" ] && [ ! -s "/root/.ssh/id_rsa" ] && [ ! -s "/root/.ssh/id_rsa.pub" ];then
ssh-keygen -f /root/.ssh/id_rsa -P '' -q && echo 1
fi

echo '下面是autossh客户端生成的公钥(id_rsa.pub),请将其复制到autossh服务器端的/root/.ssh/authorized_keys中,使其可以免密登录。'
echo '========================'
cat /root/.ssh/id_rsa.pub
echo '========================'
echo '这个ssh认证密钥公钥是服务(脚本)启动的时候生成了(位于/root/.ssh/id_rsa和id_rsa.pub),请务必要将/root/目录(或/root/.ssh目录)做数据持久化(目录挂载),以防止每次启动服务由于找不到之前的密钥文件就会重新生成密钥,其密钥(公钥)发生变化,从而无法免密登录。'

nginx && echo 'nginx已启动,请务必将/etc/nginx/conf.d目录挂载为持久化存储(以免添加配置以后重启丢失nginx配置文件)'
crond && echo 'crond已启动' #crontab守护进程,主要用于运行nginx日志切割定期清理的脚本。
echo '如果想要nginx(容器)重启后日志文件不丢失还需要将/var/log/nginx目录挂载为持久化存储。(可选项,非必须)'


rm -rf /root/.ssh/known_hosts #每次启动时删除已信任的主机(autossh-server),容器运行的ssh服务状态容易变,例如更新的了容器之类的操作会使容器原本状态发生变化(变成新的主机),client端通过known_hosts中的主机是不能识别到新的主机的,这时会认证失败,所以这里为避免这个问题每次会删除该文件。
ssh -o StrictHostKeyChecking=no -o BatchMode=yes -p ${SERVER_SSH_PORT} ${SSH_SERVER} exit #每次启动前先用ssh命令连接一次,第一点是如果登录失败就直接结束服务,会提示先配置免密登录。第二点是这个ssh是免交互操作的(默认会提示是否保存主机,这里直接保存),如果这里不先连一次将其主机保存至known_hosts文件中的话,下面autossh连接时还会提示是否信任主机密钥的提示,但容器POD运行没有办法进行交互操作。


if [ ! $? -eq 0 ]; then
echo "SSH登录失败,请检查免密登录设置"
echo "=====提示:请务必配置好client端与server端的免密ssh登录配置再启动服务。====="
exit 1
fi


echo '启动autossh服务.......'
autossh -p ${SERVER_SSH_PORT} -M ${CLIENT_PORT} -CNR ${CLIENT_PROXY_PORT} ${SSH_SERVER} #该进程为容器前台进程
nginx_cutlog.sh

和上面autossh-server一样的日志切割及日志清除脚本,这里不多做解释。主要是client端也有个转发的nginx需要存日志。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/bin/bash
touch /tmp/1

log_dir="/var/log/nginx"
current_date=$(date "+%Y-%m-%d")
month_ago=$(date -d "-1 month" "+%Y-%m-%d")

# 遍历目录下以.log结尾的文件
for file in "$log_dir"/*.log; do
if [[ -f "$file" ]]; then
# 获取文件的基本名称(不包含路径)
base_name=$(basename "$file")

# 检查文件名中是否已经包含日期
if [[ ! "$base_name" =~ [0-9]{4}-[0-9]{2}-[0-9]{2} ]]; then
# 构造新的文件名,将日期追加到文件名中
new_file="${log_dir}/${base_name%.*}-$current_date.${base_name##*.}"

# 切割文件
mv "$file" "$new_file"

# 创建新的空日志文件
touch "$file"
fi
fi
done

# 遍历目录下以.log结尾的文件,并删除一个月以前的日志文件
find /var/log/nginx/ -mtime +30 -name "*.log" -exec rm -rf {} \;
1
2
镜像构建:
这里直接使用dockerbuild构建并push推送: docker build . -f Dockerfile -t docker-regisry.finsiot.com/finsiot/autossh-client:v1.0 && docker push docker-regisry.finsiot.com/finsiot/autossh-client:v1.0
autossh-client.yaml

下面是部署文件,这里需要注意的是该容器启动了会打印公钥打日志上面,通过查看日志将公钥内容复制到autossh-server端的/root/.ssh/authorized_keys中,然后才能正常运行该服务。至于nginx的配置这里需要手动添加,该部署yaml文件已经将/etc/nginx/conf.d/做了挂载,反向代理的时候在配置文件的proxy_pass后面指定ingress的访问路径即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
apiVersion: v1
kind: ConfigMap
metadata:
name: autossh-client-config
data:
SERVER_SSH_PORT: '30022'
CLIENT_PORT: '22222'
CLIENT_PROXY_PORT: '9999:127.0.0.1:80'
SSH_SERVER: 'root@47.108.88.235'
---
apiVersion: apps/v1 #指定api版本标签
kind: StatefulSet #定义资源的类型/角色,ReplicaSet为控制器
metadata: #定义资源的元数据信息
name: autossh-client #定义资源的名称,在同一个namespace空间中必须是唯一的
labels: #定义资源标签
app: autossh-client
spec:
serviceName: autossh-client
replicas: 1 #定义副本数量
selector: #定义选择器
matchLabels: #匹配上面的标签
tier: autossh-client #匹配模板名称
template: #定义模板
metadata:
labels:
tier: autossh-client
spec:
nodeName: node1
imagePullSecrets:
- name: docker-registry
containers: #定义容器信息
- name: autossh-client #容器名,与标签名要相同
imagePullPolicy: Always
image: docker-registry.finsiot.com/finsiot/autossh-client:v1.0 #容器使用的镜像以及版本
envFrom:
- configMapRef:
name: autossh-client-config
volumeMounts:
- mountPath: /etc/nginx/conf.d/
name: autossh-client
subPath: conf.d
- mountPath: /root/
name: autossh-client
subPath: root
- mountPath: /var/log/nginx
name: autossh-client
subPath: log
volumes:
- name: autossh-client
persistentVolumeClaim:
claimName: autossh-client-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: autossh-client-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi

需要注意的地方

这里主要强调一下部署时还需要额外做的操作。

1.从使用上来讲需要注意在autossh-client启动后将其日志打印的公钥复制到server端(否则client无法成功启动)。

2.client和server端都需要手动加入nginx的反向代理配置文件(做域名转发配置),例如这里有一个www.test.com的域名,在server端需要配置反代转发到ssh通道的端口上面,而到了client端还需要将这个域名配置反代转发到内网ingress的ip或host上面。至于内网里面也需要一个www.test.com的域名指向对应的服务,而云服务器ingress也需要一个ingress指向autossh-server。

3.autossh-client端连接到server端的几个变量根据环境情况需要改。