VS Code 自动化部署到 Linux:非 Root 用户、SSH 密钥与 Systemd 实战,提升效率与安全

如果手动将应用程序部署到远程服务器,过程可能会非常繁琐。通过自动化该过程,您可以节省时间并避免重复步骤,从而专注于编码。在本指南中,我们将设置一个便捷的工作流程,其中 Visual Studio Code (VS Code) 任务会自动构建您的应用程序,使用 SSH 密钥认证将文件安全地传输到远程 Linux 服务器(使用非 root 用户),并使用 systemd 服务重新启动应用程序。这不仅简化了部署,还通过消除对 root 登录或基于密码的 SSH 的需求来提高安全性。使用专用的非特权用户为保护您的服务器免受攻击者攻击增加了一道额外的屏障。并且通过依赖 SSH 密钥而不是密码,我们完全降低了 SSH 上的暴力破解风险。让我们深入了解分步设置过程。

1. 创建专用的非 Root 用户进行部署

root 用户身份部署或运行应用程序是一种不良做法,因此我们将在远程服务器上创建一个新用户(例如 vscodeuser​)来拥有和运行我们的应用程序。使用非 root 帐户可以限制帐户被盗用时可能造成的损害,并且出于安全考虑,建议这样做。此用户将用于 SSH 登录和运行应用程序的服务。

在远程服务器上,以 root 用户(或具有 sudo 权限的管理员用户)身份登录,然后运行以下命令添加新用户(将 vscodeuser​ 替换为您想要的用户名):

sudo adduser vscodeuser

此命令将以交互方式提示您输入密码和用户详细信息。选择一个强密码(尽管我们很快将配置基于密钥的登录,因此 SSH 不需要密码)。您可以接受用户信息的默认值或跳过它们。完成后,将创建一个新帐户及其主目录(例如 /home/vscodeuser​)。

可选: 如果您希望此用户具有执行管理任务的 sudo 权限,请将其添加到 sudo 组(在 Debian/Ubuntu 上):

sudo usermod -aG sudo vscodeuser

但是,对于纯粹的部署用户,通常不需要授予 sudo 权限。

为什么不使用 root? 默认情况下,每个 Unix/Linux 系统都有一个 root 帐户,攻击者通常会将其作为目标。禁止直接 root SSH 访问并改用普通用户意味着攻击者必须首先攻破非 root 帐户,然后才能提升权限,从而增加了一层额外的安全性。完全禁用 root 用户的 SSH 登录,并使用经过密钥认证的非特权帐户进行远程访问,这被认为是一种非常好的做法。

2. 为新用户设置 SSH 密钥认证

接下来,我们将为 vscodeuser​ 启用 SSH 公钥认证,这样我们就可以无需密码登录。基于密钥的登录不仅更方便(在我们的自动化任务中无需密码提示),而且比密码登录更安全,尤其是在您完全禁用服务器上的密码身份验证的情况下。

在您的本地计算机上, 您需要一个 SSH 密钥对(一个私钥和一个公钥)。如果您已经有一个想要使用的 SSH 密钥,则可以跳过生成新密钥的步骤。否则,请生成一个新的密钥对(我们将在步骤 4 中介绍)。现在,假设您已准备好公钥(例如,在本地系统上的 ~/.ssh/id_rsa.pub​ 中)。

在远程服务器上, 为新用户授权您的公钥:

  • 在新用户的主目录中创建 .ssh目录(并设置正确的权限): 以 root 用户身份登录(或使用 sudo​)并运行:

    sudo mkdir -p /home/vscodeuser/.ssh
    sudo chmod 700 /home/vscodeuser/.ssh
    

    这将创建 .ssh​ 目录并限制其访问权限,以便只有该用户可以访问它(权限 700 表示所有者具有读/写/执行权限,其他人无权访问)。

  • 将您的公钥添加到用户的 authorized_keys文件中: 如果您有可用的公钥文本,可以手动添加。例如:

    sudo sh -c 'echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDLVDBIpdpfePg/...(密钥的其余部分)...fRjB user@local" > /home/vscodeuser/.ssh/authorized_keys'
    

    (将引号中的 ssh-rsa 密钥替换为您的实际公钥字符串。它应该全部在一行上。) 或者,使用 sudo nano /home/vscodeuser/.ssh/authorized_keys​ 在编辑器中打开文件并粘贴公钥行。

  • 为密钥文件设置正确的文件权限并将所有权分配给新用户

    sudo chmod 600 /home/vscodeuser/.ssh/authorized_keys
    sudo chown -R vscodeuser:vscodeuser /home/vscodeuser/.ssh
    

    authorized_keys​ 文件应具有 600 权限(所有者具有读/写权限,其他人无权访问),并且它必须由该用户拥有。chown 命令确保 vscodeuser 拥有 .ssh` 文件夹及其内容。不正确的权限或所有权将导致 SSH 出于安全原因忽略该密钥。

您的远程服务器现在已配置为允许 vscodeuser​ 使用 SSH 密钥登录。我们有效地执行了与 ssh-copy-id​ 实用程序相同的步骤:创建 ~/.ssh​,将密钥添加到 authorized_keys​,并修复权限。如果 ssh-copy-id​ 在您的本地系统上可用,您可以使用它一次性自动完成此步骤,因为许多教程都推荐这种便捷方法:

# 从您的本地计算机运行:
ssh-copy-id vscodeuser@your_server_ip

ssh-copy-id​ 命令将提示您输入用户密码(仅此一次),然后将您的公钥传输到服务器,并为您设置文件权限。之后,您应该能够无需密码通过 SSH 登录。

安全提示: 由于我们现在为 vscodeuser​ 设置了基于密钥的身份验证,因此请考虑在 SSH 服务器上禁用密码身份验证并禁止 root 登录。编辑 /etc/ssh/sshd_config​ 并设置 PasswordAuthentication no​ 和 PermitRootLogin no​,然后重新启动 SSH。这可确保所有登录都必须使用密钥(攻击者无法再进行暴力破解密码,并且无法直接针对 root 帐户)。您将以 vscodeuser​ 身份登录,并在需要 root 权限时使用 sudo​。

3. 验证 SSH 权限和所有权

让我们仔细检查新用户的 SSH 配置是否正确,因为这是常见的故障点。在远程服务器上(以 root 用户身份或通过 sudo):

  • 确保 /home/vscodeuser/.ssh vscodeuser所有并且具有 700 (drwx------) 权限。
  • 确保 /home/vscodeuser/.ssh/authorized_keys vscodeuser所有并且具有 600 (-rw-------) 权限。

您可以使用以下命令进行验证:

ls -l /home/vscodeuser/.ssh
ls -l /home/vscodeuser/.ssh/authorized_keys

您应该看到指示所有者和权限的输出,例如:

drwx------ 2 vscodeuser vscodeuser 4096 May 24 00:30 .ssh
-rw------- 1 vscodeuser vscodeuser  382 May 24 00:30 authorized_keys

如果出现任何问题(例如,文件归 root 所有或具有更广泛的权限),请使用步骤 2 中的 chmod​ 和 chown​ 命令进行修复。正确的文件模式至关重要,因为默认情况下,SSH 服务器的 StrictModes 设置会在权限过于开放或所有权不正确时拒绝使用密钥。

此时,尝试从本地计算机登录以测试设置(您可能需要先生成或指定您的私钥,我们将在下一步中执行此操作):

# 在本地计算机上
ssh vscodeuser@your_server_ip

如果一切设置正确,您应该能够登录到服务器而无需提示输入密码。(如果您确实收到提示,则表示密钥身份验证失败——请重新检查前面的步骤。)

4. 在本地计算机上生成和使用 SSH 密钥对

如果您尚未在本地开发计算机上创建 SSH 密钥对,请立即创建。私钥保留在您的本地计算机上(妥善保管!),而公钥是我们安装在服务器上的那个。我们将演示使用 RSA 密钥(这很常见),但您也可以使用 Ed25519 密钥。最新版本的 ssh-keygen​ 默认使用 3072 位 RSA 密钥,这对于大多数用例来说是安全的。

在您的本地计算机上,打开一个终端并运行:

ssh-keygen -t rsa -b 4096 -C "VSCode Deploy Key"
  • -t rsa -b 4096​:指定 RSA 密钥类型,长度为 4096 位(用于更强的加密)。
  • -C "VSCode Deploy Key" ​:向密钥添加注释(有助于以后识别密钥,例如,在 authorized_keys​ 中会显示注释)。

系统将提示您选择保存密钥的文件位置。按 Enter 接受默认值(保存为 ~/.ssh/id_rsa​ 和 ~/.ssh/id_rsa.pub​),或指定自定义路径(例如,~/.ssh/id_vscode_deploy​ 用于特定于项目的密钥)。接下来,您可以输入密码短语来保护私钥。建议使用密码短语以确保安全(它会加密磁盘上的私钥),但如果您确实设置了密码短语,则需要使用 SSH 代理,以便自动化过程可以在没有手动输入密码短语的情况下使用密钥。为简单起见,您可以选择不设置密码短语(只需按 Enter)——但请记住,这意味着如果有人获得了您的密钥文件,他们就可以使用它。(如果您确实设置了密码短语,VS Code 可以利用 SSH 代理提示您一次并缓存它。)

完成后,您将拥有两个文件,例如:

  • ~/.ssh/id_rsa​ – 私钥(保密;请勿共享)。
  • ~/.ssh/id_rsa.pub​ – 公钥(将其复制到服务器的 authorized_keys​ 中,就像我们之前做的那样)。

如果您在此步骤中生成了新密钥,则需要返回步骤 2 并将公钥放在服务器上(放入 /home/vscodeuser/.ssh/authorized_keys​ 中)。您可以使用 ssh-copy-id​ 或如前所述的手动复制。一旦服务器拥有公钥,请再次测试登录:

ssh vscodeuser@your_server_ip -i ~/.ssh/id_rsa

(如果您没有将其保存为默认密钥,-i​ 选项指定要使用的私钥文件。)这应该可以让您无需密码即可登录。从现在开始,我们的 VS Code 任务可以使用此密钥自动进行身份验证。

为清楚起见,以下是密钥文件的外观(示例):

  • 公钥 (id_rsa.pub) – 一个单行条目,以密钥类型(例如 ssh-rsa​ 或 ssh-ed25519​)开头,后跟 base64 密钥和可选注释。例如:

    ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCxqHYEE3XdexSmo5rT01nxOKjP4YOIuC/yLEq50bUHSSOoWoaRPP0ZIe+ZSpYd/Em18igpm5uKtPZHeKe1sYhffHLmFknS1BMaFACiC/TLR33Zuf4yS6X4mtpNNRtWeD3KIt7WcY9yKrAUrBq12OMifphHdQHtWkJ0JfqVTQMeExsJ8KR4GDp8TWKkyuyS+UiZ9I2PoomAIhAcQLqaCQ5dfWLzG/w5O6cd+TsJsOghWVfiDfCq5g4S4ODDZ5Z/cd15lsnXijvs50JtxnXDTK37mXi1PXGnW8jtRVjrW71Bdmsn+euRJEhw4+ynBAbJ2dqmDxMDKVo7OzWxrx5aSHzT demo@local
    

    这整行都将位于服务器的 authorized_keys​ 中。(以上是用于说明的虚拟示例密钥。)

  • 私钥 (id_rsa) – 一个 PEM 编码的文件,必须保留在您的本地计算机上。切勿共享它。它看起来像一个文本块,以特定标记开头和结尾:

    -----BEGIN OPENSSH PRIVATE KEY-----
    b3BlbnN...AAAABG5vbmUAAA...<base64 data>...
    ... (几行编码的密钥数据) ...
    -----END OPENSSH PRIVATE KEY-----
    

    设置自动化任务时,请确保 VS Code/您的 SSH 客户端可以访问此文件。为方便起见,您可以将密钥添加到 SSH 代理(ssh-add ~/.ssh/id_rsa​),以便 VS Code 无需提示即可使用它。请妥善保管此文件!

现在,vscodeuser​ 的基于 SSH 密钥的身份验证已正常工作,我们可以继续设置应用程序在此用户控制下运行并自动化部署。

5. 为应用程序设置 Systemd 服务(用户服务)

与其在服务器上手动运行应用程序,不如将其设置为 systemd 服务。这样,它可以在启动时启动,在发生故障时重新启动,并且可以通过单个命令轻松控制(启动/停止/重新启动)。我们将创建一个在我们的 vscodeuser​(非 root)帐户下运行的服务,使用 systemd 的用户服务功能。

使用 systemd 用户服务意味着该服务与用户绑定,并且不需要 root 权限即可管理。将单元文件放在 ~/.config/systemd/user/​ 中允许该用户无需 sudo 即可启动/停止它。这非常适合我们的部署场景:我们希望 vscodeuser​(以及我们通过 SSH 以该用户身份进行的自动化)能够重新启动应用程序,而无需授予完全的 root 访问权限。

在远程服务器上(以 vscodeuser身份登录):

  • 为您的应用创建 systemd 单元文件。 首先,为用户单元创建目录并打开一个新的服务文件:

    mkdir -p ~/.config/systemd/user
    nano ~/.config/systemd/user/myapp.service
    

    (您可以随意命名服务文件,这里我们使用 “myapp.service” 作为示例。)

  • 在文件中定义服务配置。 例如:

    [Unit]
    Description=My App Service
    After=network.target
    
    [Service]
    ExecStart=/home/vscodeuser/myapp/myappbinary
    WorkingDirectory=/home/vscodeuser/myapp
    Restart=on-failure
    # User=usernotneeded here, since this is a user service
    
    [Install]
    WantedBy=default.target
    
    

    让我们分解一下这个例子:

    • ExecStart: 运行您的应用程序的命令。这可以是一个可执行文件或一个脚本。调整路径到您部署的二进制文件/应用程序将驻留的位置。例如,如果您在本地编译一个二进制文件并计划将其 rsync 到 /home/vscodeuser/myapp/​,请在此处设置该路径。
    • WorkingDirectory: (可选)如果需要,将其设置为您的应用程序目录,以便服务在该目录中运行。
    • Restart: 设置为 on-failure​(或 always​)以在应用程序崩溃时自动重新启动它。
    • 我们在这里没有指定 User=​,因为该服务将凭借其位于用户服务文件夹中而在用户上下文中运行。(如果我们在 /etc/systemd/system​ 下创建系统范围的服务,我们会在 Service 部分包含 User=vscodeuser​ 以便以该用户身份运行它。但在这里 systemd 知道它是一个每用户服务。)
  • 重新加载 systemd(用户)配置并启用服务:

    systemctl --user daemon-reload        
    systemctl --user enable myapp.service --now
    

    以上命令将启用服务的自动启动,并立即启动它 (--now​)。--user​ 标志告诉 systemctl 对用户服务进行操作(无需 sudo)。如果服务成功启动,您的应用程序现在应该正在运行。您可以使用以下命令检查其状态:

    systemctl --user status myapp.service
    

    这将显示服务在 vscodeuser​ 下运行,确认它不需要 root 权限。

  • 启用 lingering(可选但建议用于长时间运行的服务):默认情况下,systemd 中的用户服务在用户登录时启动,并在用户会话结束时停止。如果您希望即使 vscodeuser​ 没有活动登录(例如,在没有交互式登录的情况下重新启动后),您的应用程序也能运行,则需要为该用户启用 lingering。Lingering 允许用户的 systemd 实例在启动时运行并保持运行。使用以下命令启用它:

    sudo loginctl enable-linger vscodeuser
    

    (这是此设置中少数几个需要 sudo 的命令之一,因为您正在修改该用户的系统设置。)一旦启用 linger,myapp.service​ 将在启动时为该用户启动,无需任何人登录。这也意味着如果您从 SSH 会话中注销,服务不会停止(而没有 lingering,systemd 会在最后一个会话关闭时停止用户服务)。启用 linger 实质上是告诉系统“让该用户的服务在后台保持运行”。

有了 systemd 服务,您就有了一种管理应用程序进程的可靠方法。vscodeuser​ 现在可以使用简单的命令控制服务(无需 root)。例如,如果您更新了应用程序二进制文件,可以使用以下命令重新启动服务:

# (以 vscodeuser 身份运行,或通过 SSH 以 vscodeuser 身份运行)
systemctl --user restart myapp.service

这正是我们将在下一步中从 VS Code 自动化的操作。

6. 使用 VS Code 任务自动化构建和部署

现在是神奇的部分:配置 VS Code 以通过一个命令或键盘快捷键执行所有这些步骤(构建、部署、重新启动)。VS Code 有一个内置的任务运行器,可以调用外部工具和脚本。我们将使用 tasks.json 配置来定义一系列任务:

  • 一个用于构建或编译您的项目的任务(如果需要)。
  • 一个用于将构建工件部署到服务器的任务(使用 rsync​ 通过 SSH)。
  • 一个用于重新启动远程 systemd 服务的任务。

通过链接这些任务,您可以一次性完成整个部署。这可以节省时间并确保一致性(不会忘记某个步骤),从而有效地在您的编辑器中实现一个简单的类似 CI/CD 的管道。

打开您工作区的 .vscode/tasks.json​(如果不存在则创建它)。定义类似于以下示例的任务(实际上你可以用&&将命令连接起来,但是不能分行。):

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Build App",
            "type": "shell",
            "command": "go build -o myapp .",
            "group": { "kind": "build", "isDefault": true },
            "problemMatcher": []
        },
        {
            "label": "Deploy App",
            "type": "shell",
            "command": "rsync -avz ./myapp vscodeuser@your-server-ip:/home/vscodeuser/myapp/",
            "dependsOn": "Build App",
            "problemMatcher": []
        },
        {
            "label": "Restart App Service",
            "type": "shell",
            "command": "ssh vscodeuser@your-server-ip 'systemctl --user restart myapp.service'",
            "dependsOn": "Deploy App",
            "problemMatcher": []
        }
    ]
}

让我们分解一下这里发生的事情:

  • Build App (构建应用) :这是一个编译应用程序的任务。在此示例中,我们假设是一个 Go 项目(生成名为 myapp​ 的二进制文件)。根据您的实际构建过程调整命令(对于 Node 应用程序可能是 npm run build​,对于 Java 应用程序可能是 mvn package​ 等)。我们将其标记为 "Build App" 并将其标记为默认构建任务。problemMatcher: []​ 设置为空数组以避免 VS Code 尝试解析错误(因为我们的部署任务可能不会产生典型的编译器错误)。

  • Deploy App (部署应用) :此任务使用 rsync​ 将构建的文件同步到远程服务器。命令 rsync -avz ./myapp vscodeuser@your-server-ip:/home/vscodeuser/myapp/​ 会将 myapp​ 二进制文件(或者您可以指定一个目录)传输到远程路径。我们使用 -a​ 标志表示归档模式(保留权限等),-v​ 表示详细输出,-z​ 表示在传输过程中压缩数据。如果您希望删除服务器上本地不再存在的文件(对于同步整个目录很有用),还可以添加 --delete​。可以指定 -e ssh​ 选项来自定义 SSH 命令(例如,指定密钥或非标准端口),但如果您的 SSH 配置是标准的并且您的密钥是默认的或已加载到代理中,rsync​ 将自动使用它。此任务声明它 dependsOn "Build App" 任务。这意味着 VS Code 将首先运行 "Build App",然后运行 "Deploy App"。rsync 的输出将出现在 VS Code 终端中;成功后,您将看到它列出已传输的文件。

  • Restart App Service (重启应用服务) :此任务通过 SSH 连接并为我们的服务运行 systemd 重启命令。它依赖于 "Deploy App",因此只有在成功部署后才会运行。命令是:

    ssh vscodeuser@your-server-ip 'systemctl --user restart myapp.service'
    

    当此命令运行时,它将使用基于密钥的身份验证(因此没有密码提示)并指示 systemd 重新启动我们在步骤 5 中创建的服务。命令周围的单引号确保它作为单个远程 shell 命令运行。如果您的 SSH 使用自定义身份文件或端口,您可以根据需要包含诸如 ssh -i ~/.ssh/id_rsa -p 2222 ...​ 之类的选项。此任务完成后,您的新代码应该已在服务器上运行!

添加这些任务后,您可以在 VS Code 中执行它们。例如,打开命令面板 (⇧⌘P / Ctrl+Shift+P) 并运行“Tasks: Run Task (任务:运行任务) ”,然后选择 "Restart App Service"。这将触发整个链:构建 → 部署 → 重启。如果您将某个任务标记为构建组,还可以绑定一个键或使用默认的构建快捷方式 (Ctrl+Shift+B)。

工作原理: VS Code 的任务运行器允许对任务进行分组和排序。在我们的配置中,"Restart App Service" 是最终的编排任务。运行它会导致其依赖项 "Deploy App" 首先运行,而后者又会首先运行 "Build App"。任务按顺序运行,每个任务仅在前一个任务成功后才运行。这可确保您的应用程序仅在正确编译和同步新构建后才重新启动。所有输出(构建输出、rsync 进度等)都将在集成终端中可见,因此您可以监控正在发生的情况。

注意: 上述任务假定为非交互式环境(无密码提示)。我们通过使用 SSH 密钥来设置它。如果您确实为私钥设置了密码短语,请确保代理正在缓存它(例如,如果您在 Windows 上,则为 Pageant 或 Windows OpenSSH 代理;在 macOS/Linux 上,则为 ssh-agent)。否则,任务可能会因等待您输入密码短语而挂起。VS Code 中的一个解决方案是使用 "presentation": { "reveal": "always", "focus": false, "panel": "shared" }​ 设置以确保显示终端,但最好事先解锁密钥。

总结

通过此配置,您的部署工作流程将大大简化。您可以编写代码,然后在 VS Code 中通过一个命令或按键自动执行以下操作:

  • 编译/构建您的项目(如果适用)。
  • 将更新的文件安全地复制到服务器(使用 rsync​ 通过 SSH 进行密钥身份验证,无需密码)。
  • 重新启动服务器上的应用程序服务。

所有这些都无需每次手动 SSH,无需输入密码,也无需为您的用户授予不必要的 root 权限。非 root 用户 vscodeuser​ 管理自己的服务,从而提高了安全性(应用程序不会意外地以 root 用户身份运行,攻击者也无法直接针对 root 登录)。基于密钥的 SSH 身份验证意味着自动化任务可以安全运行,并且您已经消除了服务器上暴力破解密码攻击的风险。

此设置非常适合独立开发人员或小型团队:就像拥有一个轻量级的部署管道。而且这一切都是通过您控制的简单工具和脚本配置的。此外,通过使用 VS Code 任务,您可以将整个构建/部署过程保留在编辑器中——这既方便又不易出错(不会忘记复制文件或重新启动服务)。

你可能也感兴趣