目录一、SSH 介绍二、Jenkins存放秘钥的方式三、部署到ECS时的常见问题四、Podman on ECS Jenkins 落地方案一、SSH 介绍SSHSecure Shell 是一种在网络上安全登录远程电脑、并在上面执行命令的协议/工具。日常说的用 SSH通常指ssh user服务器IP连上之后就像坐在那台机器前一样操作终端。它解决什么问题早期远程登录常用 Telnet内容几乎是明文密码、命令都可能被窃听。SSH 的核心价值加密传输通信内容加密中间人难以直接偷看身份认证确认“你是谁”、以及“这台服务器是不是你要连的那台”远程执行不只登录还能远程跑命令、传文件所以在运维、Jenkins 部署到 ECS 时SSH 很常用。基本组成角色说明SSH 客户端本机 / Jenkins 所在机器上的sshSSH 服务端目标机器上的sshd监听常见端口 22用户账号如centos、deploy认证方式密码或更推荐的密钥一次连接大致是电脑/Jenkins --加密通道-- ECS 上的 sshd --验证身份-- 给出一个远程 Shell两种常见登录方式1. 密码登录ssh centos192.168.1.10 # 然后输入密码简单但密码可能泄露、也不适合自动化。2. 密钥登录推荐本地生成一对密钥私钥private key只有你自己保管不能泄露公钥public key放到服务器的~/.ssh/authorized_keys之后登录不再输密码ssh -i ~/.ssh/id_rsa centos192.168.1.10Jenkins 部署 ECS 时通常把私钥存进Jenkins 凭据流水线里用它去 SSH。常用能力不只是登录1、远程执行一条命令ssh userecs-ip sudo systemctl restart myapp2、传文件# 上传 scp app.jar userecs-ip:/opt/app/ # 下载 scp userecs-ip:/var/log/app.log ./更强的同步工具还有rsync底层也常走 SSH。3、隧道/端口转发进阶把远程服务“映射”到本地例如安全访问远程数据库端口。和 Jenkins / ECS 的关系在Jenkins 部署到 ECS里SSH 通常负责最后一步Jenkins 构建完成→ 通过 SSH 连上 ECS→ scp/rsync 传包或让机器 docker pull→ ssh 远程执行重启、健康检查所以SSH安全通道怎么连、怎么传、怎么远程执行Ansible在 SSH 之上做批量、标准化部署kubectl那是连 K8s不是 SSH 那套路安全上要注意什么私钥当密码看泄露等于别人能登录你的机器尽量禁用密码登录只保留密钥不要用 root 日常部署用权限受限的部署账号限制来源 IP / 安全组不要对公网无脑敞开 22 端口换默认端口、加 fail2ban 等属于加固手段不是必须第一步就做几个容易混淆的词词含义SSH协议/工具本身sshd服务器上的 SSH 服务进程Shell命令行环境bash 等SSH 是“安全地获得远程 Shell”SSL/TLS另一类加密协议常用于 HTTPS和 SSH 不是同一个东西二、Jenkins存放秘钥的方式Jenkins 通过 SSH 访问 ECS 时私钥不要当普通文件随便扔在某个共享目录里长期使用。正确做法是放进Jenkins 凭据Credentials由 Jenkins 加密保管流水线按 ID 引用。「都放在同一个目录」——可以临时用但有明显风险生产不推荐。推荐存在 Jenkins Credentials 里怎么存Manage Jenkins → Credentials凭据添加 SSH Username with private key填Username如deployPrivate Key粘贴私钥内容或从文件录入记下凭据 ID如ecs-ssh-key流水线怎么用sshagent(credentials: [ecs-ssh-key]) { sh ssh -o StrictHostKeyCheckingno deployECS_IP hostname }或用ssh-agent/ Publish Over SSH 等插件本质都是用凭据 ID不在脚本里写私钥路径明文。为什么这样好私钥不进 Git、不进JenkinsfileJenkins 主目录里加密保存依赖 Jenkins 自身安全机制权限可控谁能看凭据、哪个 Job 能用轮换密钥时只改凭据不用改一堆脚本路径如果放在同一目录会有什么问题比如都放在/var/lib/jenkins/.ssh/ id_ecs_a id_ecs_b id_ecs_c或某个共享目录/data/keys/问题说明权限风险目录若对多人/多 Job 可读一把钥匙被共用就扩大泄露面误用风险脚本写错文件名可能连错机器、用错账号备份/拷贝泄露目录被打包、同步、误提交到仓库都很容易出事Agent 不一致多节点构建时私钥若只在某台机器目录别的 Agent 找不到审计困难不如凭据系统能管“谁在用哪把钥匙”轮换麻烦改文件要同步所有机器和脚本所以同一目录放多把密钥文件系统上可行安全与运维上容易乱、容易漏不适合作为正式方案更稳妥的实践一把钥匙一个用途最小权限不要一把 root 私钥打天下最好deploy用户 仅部署所需 sudo每套环境/每类机器可分开ecs-test-deploy-keyecs-prod-deploy-key生产与测试分开避免测试流水线能登生产权限尽量严若必须落盘chmod 700 ~/.sshchmod 600 ~/.ssh/id_xxxchown jenkins:jenkins ~/.ssh -R私钥永不进仓库.gitignore掉也不要写在 Job 明文参数里定期轮换人员离职、怀疑泄露时立刻换钥并废止旧公钥和「同一目录」相关的直接回答做法是否建议多把私钥都丢在/var/lib/jenkins/keys/给所有 Job 读不建议每把钥进 Credentials流水线用不同 credentialId推荐开发机本地~/.ssh自己用可以那是个人环境Jenkins 正式访问 ECS用 Credentials不要靠共享目录「同一目录」本身不是协议错误问题在共享、权限、误用和泄露面。密钥一多目录方案几乎一定会乱。小提示公钥放哪私钥Jenkins Credentials保密公钥ECS 上~deploy/.ssh/authorized_keys两边成对缺一不可。用 Jenkins 凭据存私钥按环境/用途分开不要图方便把所有私钥堆在同一个可共享目录里。三、部署到ECS时的常见问题1、用哪个 Linux 用户部署要问root 还是普通用户方案专用deploy用户不要日常用 root2、私钥存在哪要问文件目录还是 Jenkins 凭据方案Jenkins Credentials不要堆在共享目录不要进 Git3、一把密钥打天下要问测试键能不能登生产方案环境隔离密钥生产单独凭据 ID4、sudo 权限怎么控要问deploy 能否随意 root方案sudoers只允许重启服务、切软链等必要命令5、known_hosts/ 主机指纹要问第一次 SSH 交互确认导致流水线卡住方案预先写入 known_hosts或发布机侧统一管理慎用长期关闭主机校验6、配置文件放哪要问配置打进包还是机器上外置方案配置与代码分离每环境一份配置不进镜像/包里的密钥7、产物是什么、放哪要问jar 直接 scp还是先推 Nexus/OSS方案正式建议先推制品库ECS 再下载/拉取便于追溯8、旧版本保留几个方案保留最近 N 个如 510支持快速回滚9、目录结构怎么设计/opt/app/ releases/ # 历史版本 current - ... # 当前软链 conf/ # 配置 logs/ # 日志方案用软链切换版本避免直接覆盖唯一文件10、触发方式方案测试环境 Webhook 自动生产建议手动或input审批11、并发部署怎么办方案同一应用加互斥锁防止两次发布互相踩12、多分支怎么发方案main→生产develop→测试禁止随意分支直发生产13、失败后哪里看方案统一看 Jenkins Console ECSjournalctl -u 服务名14、通知谁方案成功/失败钉钉或邮件至少包含环境、版本、构建链接15、能否接受短暂中断单机 stop/start 会有中断方案可接受就单机发布不可接受就多机滚动 SLB16、如何优雅停止方案先停流量从 SLB 摘除→ 停服务 → 换版本 → 启动 → 探活 → 加回流量17、健康检查做什么方案curl /health重试多次失败即回滚不要只看进程在不在18、回滚谁触发方案健康检查失败自动回滚也保留“一键回滚到上一版本”任务19、数据库变更怎么办要问是否有迁移脚本是否可回滚方案DB 变更与应用发布拆开策略先兼容再切换危险变更人工值守20、建议采用的最小可行方案MVP如果现在是小白先不要上太复杂1 台测试 ECS deploy 用户 SSH 密钥进 Jenkins 凭据systemd 跑一个 jar或一个 docker 容器Jenkins 流水线后半段固定 6 步上传 → 停服 → 切版本 → 启动 → 健康检查 → 通知保留上一版本失败可回滚测试跑顺后再复制到生产换机器、换密钥、加审批四、Podman on ECS Jenkins 落地方案总体流程代码提交→ Jenkinspodman build→ podman push 到镜像仓库→ SSH 登录 ECS→ podman pull→ 停旧容器→ 起新容器→ 健康检查→ 失败则回滚1、ECS 上先准备只做一次1安装 Podman# CentOS Stream / RHEL 9 示例 sudo dnf -y install podman podman --version2创建部署用户推荐sudo useradd -m deploy sudo mkdir -p /opt/app sudo chown deploy:deploy /opt/app3配置 SSH 免密Jenkins 用在 ECS 上sudo -u deploy mkdir -p /home/deploy/.ssh sudo -u deploy chmod 700 /home/deploy/.ssh # 把 Jenkins 公钥写入 sudo -u deploy tee -a /home/deploy/.ssh/authorized_keys 你的公钥内容 sudo -u deploy chmod 600 /home/deploy/.ssh/authorized_keys4让 deploy 能跑 Podman优先试 rootlessdeploy 自己跑# 以 deploy 登录后 podman info若 rootless 在所处环境不好用再退而求其次给有限 sudo按需# 仅示例允许 deploy 执行 podman生产请更精细 echo deploy ALL(root) NOPASSWD: /usr/bin/podman | sudo tee /etc/sudoers.d/deploy-podman5登录镜像仓库ECS 上podman login registry.example.com # 输入仓库账号/密码可把登录信息留给deploy用户避免每次交互。6安全组放行 Jenkins → ECS 的 SSH放行业务端口如 8080/80/4432、项目里准备 Dockerfile项目根目录示例FROM openjdk:21-jdk-slim WORKDIR /app COPY target/app.jar /app/app.jar EXPOSE 8080 ENTRYPOINT [java,-jar,/app/app.jar]前端/其他语言同理关键是构建上下文和端口要固定。3、镜像命名规范务必统一registry.example.com/myproj/app:1.0.0-b128-a1b2c3d registry.example.com/myproj/app:latest-test # 可选仅测试建议至少包含项目名构建号或版本号Git commit 短 SHA4、ECS 上的运行约定统一容器名、端口、数据目录APP_NAMEapp HOST_PORT8080 CONTAINER_PORT8080 DATA_DIR/opt/app/data CONF_DIR/opt/app/conf首次建目录mkdir -p /opt/app/data /opt/app/conf /opt/app/releases记录当前版本方便回滚/opt/app/releases/current_tag.txt /opt/app/releases/previous_tag.txt5、ECS 部署脚本建议做成文件放到 ECS/opt/app/deploy.sh#!/usr/bin/env bash set -euo pipefail APP_NAMEapp IMAGE_REPOregistry.example.com/myproj/app NEW_TAG$1 # 例如 1.0.0-b128-a1b2c3d HOST_PORT8080 CONTAINER_PORT8080 HEALTH_URLhttp://127.0.0.1:${HOST_PORT}/health RELEASE_DIR/opt/app/releases mkdir -p $RELEASE_DIR NEW_IMAGE${IMAGE_REPO}:${NEW_TAG} OLD_TAG$(cat ${RELEASE_DIR}/current_tag.txt 2/dev/null || true) echo [1] pull image: $NEW_IMAGE podman pull $NEW_IMAGE echo [2] record previous tag if [ -n ${OLD_TAG} ]; then echo $OLD_TAG ${RELEASE_DIR}/previous_tag.txt fi echo [3] stop old container podman stop $APP_NAME 2/dev/null || true podman rm $APP_NAME 2/dev/null || true echo [4] start new container podman run -d \ --name $APP_NAME \ --restartalways \ -p ${HOST_PORT}:${CONTAINER_PORT} \ -v /opt/app/conf:/app/conf:ro \ -v /opt/app/data:/app/data \ $NEW_IMAGE echo [5] health check ok0 for i in $(seq 1 20); do if curl -fsS $HEALTH_URL /dev/null 21; then ok1 break fi sleep 3 done if [ $ok -ne 1 ]; then echo health check failed, rollback... podman stop $APP_NAME 2/dev/null || true podman rm $APP_NAME 2/dev/null || true if [ -n ${OLD_TAG} ]; then podman run -d \ --name $APP_NAME \ --restartalways \ -p ${HOST_PORT}:${CONTAINER_PORT} \ -v /opt/app/conf:/app/conf:ro \ -v /opt/app/data:/app/data \ ${IMAGE_REPO}:${OLD_TAG} echo rolled back to ${OLD_TAG} fi exit 1 fi echo $NEW_TAG ${RELEASE_DIR}/current_tag.txt echo deploy success: $NEW_TAG授权chmod x /opt/app/deploy.sh若健康检查路径不是/health改HEALTH_URL。6、Jenkins 流水线Podman 版1Jenkins 节点也要有 Podman构建机Jenkins controller 或 agent安装 Podman并能podman login。2凭据准备ecs-ssh-keySSH 私钥访问 ECSregistry-cred镜像仓库账号Username/Password3Jenkinsfile示例pipeline { agent any environment { REGISTRY registry.example.com IMAGE_NAME myproj/app ECS_IP 192.168.1.10 // 改成你的 ECS ECS_USER deploy IMAGE_TAG b${BUILD_NUMBER}-${GIT_COMMIT.take(7)} FULL_IMAGE ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} } stages { stage(Checkout) { steps { checkout scm } } stage(Build App) { steps { // 按你的语言改这里以 Java 为例 sh mvn -B clean package -DskipTests } } stage(Build Image (Podman)) { steps { sh podman build -t ${FULL_IMAGE} . } } stage(Push Image) { steps { withCredentials([usernamePassword( credentialsId: registry-cred, usernameVariable: REG_USER, passwordVariable: REG_PASS )]) { sh echo $REG_PASS | podman login $REGISTRY -u $REG_USER --password-stdin podman push $FULL_IMAGE } } } stage(Deploy to ECS) { steps { sshagent(credentials: [ecs-ssh-key]) { sh ssh -o StrictHostKeyCheckingaccept-new ${ECS_USER}${ECS_IP} \ /opt/app/deploy.sh ${IMAGE_TAG} } } } } post { success { echo 部署成功: ${FULL_IMAGE} } failure { echo 部署失败请看 Console 和 ECS 上 podman logs app } } }需要思考的关键问题问题建议rootless 还是 rootful先 rootlessWSL/部分 ECS 不稳再 rootful和 Docker 命令差异大多可替换Compose 用podman compose镜像仓库地址生产尽量内网仓库更快更稳配置怎么进容器-v /opt/app/conf:/app/conf别把密钥打进镜像数据会不会丢数据目录必须挂卷端口冲突一台机一应用端口规划清楚重启策略--restartalways机器重启容器自动起回滚脚本里保留previous_tag.txt日志怎么看podman logs -f app7、常用运维命令ECS 上podman ps podman logs -f app podman inspect app podman stop app podman rm app # 手动回滚到上一版 PREV$(cat /opt/app/releases/previous_tag.txt) /opt/app/deploy.sh $PREV8、最小验收步骤本地/Jenkins 能podman build能podman pushECS 能podman pull手动执行/opt/app/deploy.sh 某测试tag浏览器或curl访问健康检查成功再让 Jenkins 全自动跑一遍9、起步顺序别一次做完所有环境先在 一台测试 ECS 跑通镜像仓库先用测试项目Jenkins 只接测试环境稳定后复制到生产换ECS_IP、换 SSH 凭据、加input审批