使用Jenkins时的常见问题以及Podman容器+Jenkins流水线落地方案

目录

[一、SSH 介绍](#一、SSH 介绍)

二、Jenkins存放秘钥的方式

三、部署到ECS时的常见问题

[四、Podman on ECS + Jenkins 落地方案](#四、Podman on ECS + Jenkins 落地方案)


一、SSH 介绍

SSH(Secure Shell) 是一种在网络上安全登录远程电脑、并在上面执行命令的协议/工具。

日常说的用 SSH,通常指:

bash 复制代码
ssh user@服务器IP

连上之后,就像坐在那台机器前一样操作终端。

它解决什么问题?

早期远程登录常用 Telnet,内容几乎是明文,密码、命令都可能被窃听。

SSH 的核心价值:

  1. 加密传输:通信内容加密,中间人难以直接偷看
  2. 身份认证:确认"你是谁"、以及"这台服务器是不是你要连的那台"
  3. 远程执行:不只登录,还能远程跑命令、传文件

所以在运维、Jenkins 部署到 ECS 时,SSH 很常用。

基本组成

角色 说明
SSH 客户端 本机 / Jenkins 所在机器上的 ssh
SSH 服务端 目标机器上的 sshd(监听常见端口 22)
用户账号 centosdeploy
认证方式 密码,或更推荐的密钥

一次连接大致是:

电脑/Jenkins --加密通道--> ECS 上的 sshd --验证身份--> 给出一个远程 Shell

两种常见登录方式

1. 密码登录

bash 复制代码
ssh centos@192.168.1.10

# 然后输入密码

简单,但密码可能泄露、也不适合自动化。

2. 密钥登录(推荐)

本地生成一对密钥:

  • 私钥(private key):只有你自己保管,不能泄露
  • 公钥(public key):放到服务器的 ~/.ssh/authorized_keys

之后登录不再输密码:

bash 复制代码
ssh -i ~/.ssh/id_rsa centos@192.168.1.10

Jenkins 部署 ECS 时,通常把私钥存进Jenkins 凭据,流水线里用它去 SSH。

常用能力(不只是登录)

1、远程执行一条命令

bash 复制代码
ssh user@ecs-ip 'sudo systemctl restart myapp'

2、传文件

bash 复制代码
# 上传

scp app.jar user@ecs-ip:/opt/app/

# 下载

scp user@ecs-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 那套路

安全上要注意什么

  1. 私钥当密码看:泄露等于别人能登录你的机器
  2. 尽量禁用密码登录,只保留密钥
  3. 不要用 root 日常部署,用权限受限的部署账号
  4. 限制来源 IP / 安全组,不要对公网无脑敞开 22 端口
  5. 换默认端口、加 fail2ban 等属于加固手段,不是必须第一步就做

几个容易混淆的词

含义
SSH 协议/工具本身
sshd 服务器上的 SSH 服务进程
Shell 命令行环境(bash 等);SSH 是"安全地获得远程 Shell"
SSL/TLS 另一类加密协议,常用于 HTTPS;和 SSH 不是同一个东西

二、Jenkins存放秘钥的方式

Jenkins 通过 SSH 访问 ECS 时,私钥不要当普通文件随便扔在某个共享目录里长期使用。

正确做法是放进Jenkins 凭据(Credentials),由 Jenkins 加密保管,流水线按 ID 引用。

「都放在同一个目录」------可以临时用,但有明显风险,生产不推荐。

推荐:存在 Jenkins Credentials 里

怎么存

  1. Manage Jenkins → Credentials(凭据)
  2. 添加 SSH Username with private key
  3. 填:
    • Username:如 deploy
    • Private Key:粘贴私钥内容,或从文件录入
  4. 记下凭据 ID,如 ecs-ssh-key

流水线怎么用

bash 复制代码
sshagent(credentials: ['ecs-ssh-key']) {
  sh 'ssh -o StrictHostKeyChecking=no deploy@ECS_IP "hostname"'
}

或用 ssh-agent / Publish Over SSH 等插件,本质都是:用凭据 ID,不在脚本里写私钥路径明文。

为什么这样好

  • 私钥不进 Git、不进 Jenkinsfile
  • Jenkins 主目录里加密保存(依赖 Jenkins 自身安全机制)
  • 权限可控:谁能看凭据、哪个 Job 能用
  • 轮换密钥时只改凭据,不用改一堆脚本路径

如果放在同一目录,会有什么问题?

比如都放在:

bash 复制代码
/var/lib/jenkins/.ssh/

    id_ecs_a

    id_ecs_b

    id_ecs_c

或某个共享目录 /data/keys/

问题 说明
权限风险 目录若对多人/多 Job 可读,一把钥匙被共用就扩大泄露面
误用风险 脚本写错文件名,可能连错机器、用错账号
备份/拷贝泄露 目录被打包、同步、误提交到仓库都很容易出事
Agent 不一致 多节点构建时,私钥若只在某台机器目录,别的 Agent 找不到
审计困难 不如凭据系统能管"谁在用哪把钥匙"
轮换麻烦 改文件要同步所有机器和脚本

所以:

  • 同一目录放多把密钥:文件系统上可行
  • 安全与运维上:容易乱、容易漏,不适合作为正式方案

更稳妥的实践

  1. 一把钥匙一个用途(最小权限)

    • 不要一把 root 私钥打天下
    • 最好:deploy 用户 + 仅部署所需 sudo
  2. 每套环境/每类机器可分开

    • ecs-test-deploy-key
    • ecs-prod-deploy-key
      生产与测试分开,避免测试流水线能登生产
  3. 权限尽量严(若必须落盘)

    chmod 700 ~/.ssh

    chmod 600 ~/.ssh/id_xxx

    chown jenkins:jenkins ~/.ssh -R

  4. 私钥永不进仓库

    • .gitignore
    • 也不要写在 Job 明文参数里
  5. 定期轮换

    • 人员离职、怀疑泄露时立刻换钥,并废止旧公钥

和「同一目录」相关的直接回答

做法 是否建议
多把私钥都丢在 /var/lib/jenkins/keys/ 给所有 Job 读 不建议
每把钥进 Credentials,流水线用不同 credentialId 推荐
开发机本地 ~/.ssh 自己用 可以,那是个人环境
Jenkins 正式访问 ECS 用 Credentials,不要靠共享目录

「同一目录」本身不是协议错误,问题在共享、权限、误用和泄露面。密钥一多,目录方案几乎一定会乱。

小提示:公钥放哪?

  • 私钥:Jenkins Credentials(保密)
  • 公钥:ECS 上 ~deploy/.ssh/authorized_keys

两边成对,缺一不可。

用 Jenkins 凭据存私钥,按环境/用途分开;不要图方便把所有私钥堆在同一个可共享目录里。

三、部署到ECS时的常见问题

1、用哪个 Linux 用户部署?

  • 要问:root 还是普通用户?
  • 方案:专用 deploy 用户,不要日常用 root

2、私钥存在哪?

  • 要问:文件目录还是 Jenkins 凭据?
  • 方案:Jenkins Credentials;不要堆在共享目录,不要进 Git

3、一把密钥打天下?

  • 要问:测试键能不能登生产?
  • 方案:环境隔离密钥;生产单独凭据 ID

4、sudo 权限怎么控?

  • 要问:deploy 能否随意 root?
  • 方案:sudoers 只允许重启服务、切软链等必要命令

5、known_hosts / 主机指纹

  • 要问:第一次 SSH 交互确认导致流水线卡住?
  • 方案:预先写入 known_hosts,或发布机侧统一管理;慎用长期关闭主机校验

6、配置文件放哪?

  • 要问:配置打进包,还是机器上外置?
  • 方案:配置与代码分离;每环境一份配置,不进镜像/包里的密钥

7、产物是什么、放哪?

  • 要问:jar 直接 scp,还是先推 Nexus/OSS?
  • 方案:正式建议先推制品库,ECS 再下载/拉取,便于追溯

8、旧版本保留几个?

  • 方案:保留最近 N 个(如 5~10),支持快速回滚

9、目录结构怎么设计?

bash 复制代码
/opt/app/

    releases/ # 历史版本

    current -> ... # 当前软链

    conf/ # 配置

    logs/ # 日志
  • 方案:用软链切换版本,避免直接覆盖唯一文件

10、触发方式?

  • 方案:测试环境 Webhook 自动;生产建议手动或 input 审批

11、并发部署怎么办?

  • 方案:同一应用加互斥锁,防止两次发布互相踩

12、多分支怎么发?

  • 方案:main→生产,develop→测试;禁止随意分支直发生产

13、失败后哪里看?

  • 方案:统一看 Jenkins Console + ECS journalctl -u 服务名

14、通知谁?

  • 方案:成功/失败钉钉或邮件;至少包含环境、版本、构建链接

15、能否接受短暂中断?

  • 单机 stop/start 会有中断
  • 方案:可接受就单机发布;不可接受就多机滚动 + SLB

16、如何优雅停止?

  • 方案:先停流量(从 SLB 摘除)→ 停服务 → 换版本 → 启动 → 探活 → 加回流量

17、健康检查做什么?

  • 方案:curl /health 重试多次;失败即回滚,不要只看进程在不在

18、回滚谁触发?

  • 方案:健康检查失败自动回滚;也保留"一键回滚到上一版本"任务

19、数据库变更怎么办?

  • 要问:是否有迁移脚本?是否可回滚?
  • 方案:DB 变更与应用发布拆开策略;先兼容再切换,危险变更人工值守

20、建议采用的最小可行方案(MVP)

如果现在是小白,先不要上太复杂:

  1. 1 台测试 ECS + deploy 用户 + SSH 密钥进 Jenkins 凭据
  2. systemd 跑一个 jar(或一个 docker 容器)
  3. Jenkins 流水线后半段固定 6 步:上传 → 停服 → 切版本 → 启动 → 健康检查 → 通知
  4. 保留上一版本,失败可回滚
  5. 测试跑顺后,再复制到生产(换机器、换密钥、加审批)

四、Podman on ECS + Jenkins 落地方案

总体流程:

代码提交

→ Jenkins:podman build

→ podman push 到镜像仓库

→ SSH 登录 ECS

→ podman pull

→ 停旧容器

→ 起新容器

→ 健康检查

→ 失败则回滚

1、ECS 上先准备(只做一次)

(1)安装 Podman

bash 复制代码
# CentOS Stream / RHEL 9 示例

sudo dnf -y install podman

podman --version

(2)创建部署用户(推荐)

bash 复制代码
sudo useradd -m deploy

sudo mkdir -p /opt/app

sudo chown deploy:deploy /opt/app

(3)配置 SSH 免密(Jenkins 用)

在 ECS 上:

bash 复制代码
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_keys

(4)让 deploy 能跑 Podman

优先试 rootless(deploy 自己跑):

bash 复制代码
# 以 deploy 登录后

podman info

若 rootless 在所处环境不好用,再退而求其次给有限 sudo(按需):

bash 复制代码
# 仅示例:允许 deploy 执行 podman(生产请更精细)

echo 'deploy ALL=(root) NOPASSWD: /usr/bin/podman' | sudo tee /etc/sudoers.d/deploy-podman

(5)登录镜像仓库(ECS 上)

bash 复制代码
podman login registry.example.com

# 输入仓库账号/密码

可把登录信息留给 deploy 用户,避免每次交互。

(6)安全组

  • 放行 Jenkins → ECS 的 SSH
  • 放行业务端口(如 8080/80/443)

2、项目里准备 Dockerfile

项目根目录示例:

bash 复制代码
FROM openjdk:21-jdk-slim
WORKDIR /app
COPY target/app.jar /app/app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app/app.jar"]

前端/其他语言同理,关键是:构建上下文和端口要固定。

3、镜像命名规范(务必统一)

bash 复制代码
registry.example.com/myproj/app:1.0.0-b128-a1b2c3d

registry.example.com/myproj/app:latest-test # 可选,仅测试

建议至少包含:

  • 项目名
  • 构建号或版本号
  • Git commit 短 SHA

4、ECS 上的运行约定

统一容器名、端口、数据目录:

bash 复制代码
APP_NAME=app

HOST_PORT=8080

CONTAINER_PORT=8080

DATA_DIR=/opt/app/data

CONF_DIR=/opt/app/conf

首次建目录:

bash 复制代码
mkdir -p /opt/app/data /opt/app/conf /opt/app/releases

记录当前版本(方便回滚):

bash 复制代码
/opt/app/releases/current_tag.txt

/opt/app/releases/previous_tag.txt

5、ECS 部署脚本(建议做成文件)

放到 ECS:/opt/app/deploy.sh

bash 复制代码
#!/usr/bin/env bash
set -euo pipefail

APP_NAME="app"
IMAGE_REPO="registry.example.com/myproj/app"
NEW_TAG="$1"          # 例如 1.0.0-b128-a1b2c3d
HOST_PORT=8080
CONTAINER_PORT=8080
HEALTH_URL="http://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" \
  --restart=always \
  -p "${HOST_PORT}:${CONTAINER_PORT}" \
  -v /opt/app/conf:/app/conf:ro \
  -v /opt/app/data:/app/data \
  "$NEW_IMAGE"

echo "[5] health check"
ok=0
for i in $(seq 1 20); do
  if curl -fsS "$HEALTH_URL" >/dev/null 2>&1; then
    ok=1
    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" \
      --restart=always \
      -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"

授权:

bash 复制代码
chmod +x /opt/app/deploy.sh

若健康检查路径不是 /health,改 HEALTH_URL

6、Jenkins 流水线(Podman 版)

(1)Jenkins 节点也要有 Podman

构建机(Jenkins controller 或 agent)安装 Podman,并能 podman login

(2)凭据准备

  • ecs-ssh-key:SSH 私钥(访问 ECS)
  • registry-cred:镜像仓库账号(Username/Password)

(3)Jenkinsfile 示例

bash 复制代码
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 StrictHostKeyChecking=accept-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? 先 rootless;WSL/部分 ECS 不稳再 rootful
和 Docker 命令差异 大多可替换;Compose 用 podman compose
镜像仓库地址 生产尽量内网仓库,更快更稳
配置怎么进容器 -v /opt/app/conf:/app/conf,别把密钥打进镜像
数据会不会丢 数据目录必须挂卷
端口冲突 一台机一应用端口规划清楚
重启策略 --restart=always,机器重启容器自动起
回滚 脚本里保留 previous_tag.txt
日志怎么看 podman logs -f app

7、常用运维命令(ECS 上)

bash 复制代码
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 "$PREV"

8、最小验收步骤

  1. 本地/Jenkins 能 podman build
  2. podman push
  3. ECS 能 podman pull
  4. 手动执行:/opt/app/deploy.sh 某测试tag
  5. 浏览器或 curl 访问健康检查成功
  6. 再让 Jenkins 全自动跑一遍

9、起步顺序(别一次做完所有环境)

  1. 先在 一台测试 ECS 跑通
  2. 镜像仓库先用测试项目
  3. Jenkins 只接测试环境
  4. 稳定后复制到生产:换 ECS_IP、换 SSH 凭据、加 input 审批
相关推荐
X1A0RAN11 小时前
Jenkins 插件管理与备份方案
jenkins
泡沫冰@1 天前
在WSL上的部署Jenkins
jenkins
叮咚侠1 天前
docker安装的kibana+elasticsearch,突然kibana界面打不开了
运维·jenkins
fengyehongWorld2 天前
Jenkins 安装与简单配置
运维·jenkins
weixin_307779133 天前
Linux下Jenkins数据故障的系统化排查Shell脚本
linux·运维·服务器·jenkins
海兰6 天前
【Elasticsearch】工作流自动化评估
elasticsearch·自动化·jenkins
tianyuanwo6 天前
深入掌握 java -jar 命令:从基础到 Jenkins Agent 实战
java·jenkins·jar
这个需求做不了8 天前
Jenkins自动化构建与CI/CD流水线,并配置GitLab
java·ci/cd·自动化·gitlab·jenkins
007张三丰9 天前
软件测试专栏(15/20):REST Assured接口自动化框架实战
运维·自动化·jenkins·接口自动化·rest·assured