服务器(容器)开发指南——SSH打洞开发

服务器(容器)开发指南——SSH打洞开发

在进行定制化的服务开发时,我们有时候只能在固定的服务器上进行服务的开发。此时,通过命令行的方式进行开发的难度较大。我们可以考虑通过SSH打洞的方式,通过本地IDE的SSH连接功能来获取远程的环境进行代码的开发修改。 随着容器化技术的发展,越来越多的产品服务打包进容器内运行,对容器内部代码的定制化开发需求越来越多。容器本身可以简单理解为一个更轻量的虚拟机,针对容器的定制化开发的实现也可以参考服务器开发相关技术。 本文为更好的讲解SSH打洞开发的方式,采用容器化开发技术进行讲解。

SSH容器服务打包

测试服务文件

为更好的测试SSH打洞功能,特开发一个简易的Python的Flask框架脚本以供测试。 requirements.txt:

python
1
Flask==2.3.2

app.py

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

Flask在启动时,默认绑定127.0.0.1:5000的IP和端口。当启动容器的ports参数进行端口映射时,会将容器内绑定在0.0.0.0IP上的服务映射到主机端口上而不是容器内的127.0.0.1,所以我们在启动Flask或相关服务时,需要指定绑定host为0.0.0.0.entrypoint.sh

shell
1
2
3
4
5
6
7
cd /data

# 启动SSH服务
/etc/init.d/ssh start

# 启动Flask服务
python app.py
  • 启动SSH服务和Flask服务的顺序不能更改。直接调用python app.py会启动一个前台进程,若python app.py命令在前,其后的启动/etc/init.d/ssh start命令将不会执行。
  • 容器在启动时会执行CMDENTRYPOINT中指定的命令启动一个PID=1的守护进程,该进程负责整个容器的初始化,监控子进程运行情况,信号传输、退出容器等操作。当这个进程销毁时,整个容器也会停止运行,所以一般情况下,需要在启动的命令或脚本中启动一个一直运行的守护进程,保证PID=1的进程不会因为执行完毕而默认销毁导致容器的停止。
  • python app.py命令在这里主要就是作为守护进程的存在确保PID=1的进程不会主动销毁。
    • 以Flask程序作为守护进程固然可以很好的使容器运行起来,但是当我们在进行开发时,可能会多次重启Flask进程以测试代码,此时一旦最开始的Flask进程关闭,PID=1进程也会主动销毁,导致容器退出
    • 当容器内包含有Python服务时,我们可以通过Python自带的脚本启动一个简易的守护进程,其他服务也可以参考启动一个守护进程来代替应用程序的守护进程

优化后.entrypoint.sh

shell
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
cd /data

# 启动SSH服务
/etc/init.d/ssh start

# 启动Flask服务
nohup python app.py &

# 启动python自带脚本http.server作为守护进程
python -m http.server

镜像打包

镜像打包文件

shell
 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
# 接收docker build参数
ARG ROOT_PASSWORD=focus

FROM python:3.8.17

# 重新声明所有参数以继承入口的参数传递
ARG ROOT_PASSWORD

COPY ./data /data

WORKDIR /data

# 安装操作系统依赖库
RUN  \
    apt update && \
    apt -y upgrade && \
    # 安装SSH
    apt -y install sudo openssh-client openssh-server sshpass && \
    pip install -r /data/requirements.txt && \
    # root免密设置
    echo "root ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers && \
    # 设置root密码
    echo "root:${ROOT_PASSWORD:-focus}" | chpasswd && \
    # ssh配置修改
    # 允许root用户登录
    sed -i "/PermitRootLogin/c PermitRootLogin yes" /etc/ssh/sshd_config && \
    # 允许使用密码登录
    sed -i "/PasswordAuthentication/c PasswordAuthentication yes" /etc/ssh/sshd_config

CMD [ "sh", "/data/.entrypoint.sh" ]
  • 打包镜像主要是为了在容器中安装SSH相关服务,也可根据现有的镜像启动容器后在容器内安装SSH服务(需要每次新建容器后都要安装)
shell
1
2
3
4
# 允许root用户登录
sed -i "/PermitRootLogin/c PermitRootLogin yes" /etc/ssh/sshd_config
# 允许使用密码登录
sed -i "/PasswordAuthentication/c PasswordAuthentication yes" /etc/ssh/sshd_config
  • 以上命令是实现SSH打洞登录的关键,其本质就是修改/etc/ssh/sshd_config文件中的PermitRootLoginPasswordAuthentication配置

镜像打包脚本

shell
1
2
3
4
5
6
docker build . \
    --platform 'linux/amd64' \
    --label 'maintainer=focus<https://focus-wind.com/>' \
    --build-arg 'ROOT_PASSWORD=focus' \
    --file ./Dockerfile \
    --tag "python-ssh-tunnel:3.8.17-dev"

SSH打洞开发

部署带SSH的容器

通过上述操作,我们目前已经打包好了一个带有SSH服务的镜像,通过docker compose进行服务的部署。 compose.yaml配置文件

yaml
1
2
3
4
5
6
7
8
9
version: '3.8'
services:
  ssh-tunnel:
    image: python-ssh-tunnel:3.8.17-dev
    hostname: ssh-tunnel
    container_name: ssh-tunnel
    ports:
      - 8080:22
      - 5000:5000

启动容器

shell
1
docker compose up -d

SSH连接服务器(容器内部)

通过端口映射,我们将容器内部的22端口映射到了本地的8080端口上。此时,如果我们需要访问容器内部的服务,可以通过指定IP为本机来访问容器内部。

shell
1
ssh -p 8080 root@localhost

ssh-tunnel 如上所示,我们将容器内部的SSH的22端口映射到宿主机后,通过访问宿主机的相应端口即可进入到容器内部中。

SSH访问容器内的缺陷

通过上图SSH连接容器内部时的输出可以看到,SSH客户端在连接服务端时自动生成了SSH公钥。 SSH客户端在首次连接SSH服务端时,客户端会记录服务端返回的公钥保存在~/.ssh/known_hosts文件中,当再次进行SSH连接时客户端都会验证known_hosts文件中的公钥和服务端返回公钥是否一致。由于每次根据镜像新建的容器都是一个全新的容器,此时在进行SSH连接时,会导致返回的公钥和known_hosts文件中的公钥不一致,所以无法进行SSH连接。 若是因known_hosts导致的公钥不一致无法连接的问题,可以编辑~/.ssh/known_hosts文件,删除对应的IP公钥信息重新SSH连接即可。

IDE远程SSH开发

现有的IDE大部分都支持SSH远程开发,现在大部分都开发都是在IDE上进行的开发,IDE提供的环境能够很好的帮助我们提升开发效率。

VSCode远程SSH开发

VSCode自带有远程SSH功能,在VSCode的左侧功能栏的远程资源管理器中可以进行远程SSH操作。

  • 在VSCode的远程资源管理器中,在SSH管理栏右侧点击+按钮新建SSH连接

VSCode-SSH

  • VSCode会默认读取~/.ssh/config文件中已有的SSH配置,在~/.ssh/config文件中添加服务器SSH配置后,刷新即可在远程资源管理器中显示
shell
1
2
3
4
5
6
Host ssh-tunnel
    HostName localhost
    User root
    Port 8080
    # %p: Port %r: User %h: HostName
    LocalCommand ssh -p %p %r:%h
  • 通过VSCode连接服务器或内部容器后即可像平常使用VSCode开发一样用VSCode进行集成开发

Jetbrains系列产品SSH远程开发

Jetbrains系统虽然产品众多,但其使用SSH远程开发的逻辑是一致的,这里以PyCharm为例介绍Jetbrains系列产品的SSH远程开发。 Jetbrains-SSH Jetbrains-SSH-config

  • Jetbrains系列产品打开后在Remote Development的SSH中新建项目完成SSH的创建
  • Jetbrains产品可在Specify private key可选配置项中配置公钥文件
陕ICP备2023020057号
Built with Hugo
主题 StackJimmy 设计