Ayatori 容器的 SSH 改由 cloud-init 声明
- cloud-init 安装 openssh-server,装包后(defer)写 sshd_config.d drop-in 关闭密码与 root 登录;不再使用 ssh_pwauth,它会在装包前写出残缺的 sshd_config,使 UsePAM yes 落不下来 - 删除 ansible/containers.yml 与 tasks/container-ssh.yml - 两台空容器已重建,使现场与声明一致 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -117,15 +117,16 @@ Prod `192.168.10.12`(`ayatori-{dev,prod}.ad.ddupan.top`)。地址只在
|
||||
NEC IX DHCP 池(`.128–.250`)之外。⚠ 镜像模板只在创建/复制时渲染 cloud-init seed,
|
||||
对已有实例改地址不会生效(重启、`cloud-init clean` 都不行),只能重建实例。
|
||||
|
||||
初始账号 `panxiao81`,使用 `ansible/files/panxiao81.pub` 公钥登录,可 sudo;
|
||||
密码登录关闭。宿主也可使用 `incus exec local:ayatori-dev -- bash`。
|
||||
SSH 全部由 cloud-init 完成:安装 openssh-server,创建 `panxiao81`(`ansible/files/panxiao81.pub`
|
||||
公钥、免密 sudo),装包后再写 `/etc/ssh/sshd_config.d/60-homelab.conf` 关闭密码与 root 登录。
|
||||
不使用 `ssh_pwauth`:它在装包前写出残缺的 `sshd_config`,包自带的 `UsePAM yes` 落不下来,
|
||||
锁定密码的账号会被拒(2026-09-25 曾发生)。宿主也可使用 `incus exec local:ayatori-dev -- bash`。
|
||||
provider 通过本地 Unix socket 操作,执行账号须有 socket 权限。
|
||||
|
||||
```bash
|
||||
terraform -chdir=infrastructure/incus/terraform init
|
||||
terraform -chdir=infrastructure/incus/terraform plan -out=containers.tfplan
|
||||
terraform -chdir=infrastructure/incus/terraform apply -parallelism=1 containers.tfplan
|
||||
ANSIBLE_LOCAL_TEMP=/tmp/ansible-incus ansible-playbook -i infrastructure/incus/ansible/inventory/hosts.yml infrastructure/incus/ansible/containers.yml
|
||||
terraform -chdir=infrastructure/incus/terraform output containers
|
||||
terraform -chdir=infrastructure/incus/terraform plan -detailed-exitcode
|
||||
```
|
||||
@@ -144,5 +145,3 @@ cloud-init 用户设置主要在首次启动执行,修改声明不能替代后
|
||||
首次从同一远端并行创建实例时,Incus 7.5.1 曾出现 simplestreams 缓存目录
|
||||
`mkdir ... file exists` 竞争;按上面的串行 apply 执行。失败后先重新 plan,
|
||||
保留已成功创建的实例,不清理或销毁其资源。
|
||||
|
||||
`ansible/containers.yml` 通过本机 Incus exec 幂等安装并启用 SSH server。
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
- name: 准备 Ayatori 基础容器的 SSH 入口
|
||||
hosts: incus_hosts
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: 按容器协调 SSH
|
||||
ansible.builtin.include_tasks: tasks/container-ssh.yml
|
||||
loop: [ayatori-dev, ayatori-prod]
|
||||
loop_control:
|
||||
loop_var: incus_container
|
||||
@@ -1,89 +0,0 @@
|
||||
---
|
||||
- name: 等待首次用户初始化
|
||||
ansible.builtin.command:
|
||||
argv: [incus, exec, "local:{{ incus_container }}", --, cloud-init, status, --wait]
|
||||
changed_when: false
|
||||
|
||||
- name: 检查 SSH server 是否安装
|
||||
ansible.builtin.command:
|
||||
argv: [incus, exec, "local:{{ incus_container }}", --, dpkg-query, -W, '-f=${Status}', openssh-server]
|
||||
register: container_ssh_package
|
||||
changed_when: false
|
||||
failed_when: container_ssh_package.rc not in [0, 1]
|
||||
|
||||
- name: 刷新容器包索引
|
||||
ansible.builtin.command:
|
||||
argv: [incus, exec, "local:{{ incus_container }}", --, apt-get, -o, Acquire::Retries=3, update]
|
||||
when: "'install ok installed' not in container_ssh_package.stdout"
|
||||
changed_when: true
|
||||
register: container_apt_update
|
||||
retries: 3
|
||||
delay: 5
|
||||
until: container_apt_update.rc == 0
|
||||
|
||||
- name: 安装 SSH server
|
||||
ansible.builtin.command:
|
||||
argv: [incus, exec, "local:{{ incus_container }}", --env, DEBIAN_FRONTEND=noninteractive, --, apt-get, -o, Acquire::Retries=3, install, -y, --no-install-recommends, openssh-server]
|
||||
when: "'install ok installed' not in container_ssh_package.stdout"
|
||||
changed_when: true
|
||||
register: container_apt_install
|
||||
retries: 3
|
||||
delay: 5
|
||||
until: container_apt_install.rc == 0
|
||||
|
||||
# cloud-init 在未安装 sshd 时预先生成了只有 PasswordAuthentication 的配置;
|
||||
# OpenSSH 默认 UsePAM=no 会拒绝锁定密码的公钥账号,显式采用 Ubuntu 的 PAM 模式。
|
||||
- name: 读取 SSH 配置
|
||||
ansible.builtin.command:
|
||||
argv: [incus, exec, "local:{{ incus_container }}", --, cat, /etc/ssh/sshd_config]
|
||||
register: container_sshd_config
|
||||
changed_when: false
|
||||
|
||||
- name: 声明仅公钥 SSH 与 PAM 账号检查
|
||||
ansible.builtin.command:
|
||||
argv: [incus, exec, "local:{{ incus_container }}", --, tee, /etc/ssh/sshd_config]
|
||||
stdin: "{{ container_sshd_desired }}"
|
||||
vars:
|
||||
container_sshd_desired: |
|
||||
UsePAM yes
|
||||
PubkeyAuthentication yes
|
||||
PasswordAuthentication no
|
||||
KbdInteractiveAuthentication no
|
||||
PermitRootLogin no
|
||||
Subsystem sftp internal-sftp
|
||||
when: container_sshd_config.stdout | trim != container_sshd_desired | trim
|
||||
register: container_sshd_write
|
||||
changed_when: true
|
||||
|
||||
# 24.04 新装时只有 ssh.socket 运行,/run/sshd 要等 ssh.service 的 RuntimeDirectory 才创建,
|
||||
# 缺它时 sshd -t 直接失败;按同样的 0755 预建(/run 为 tmpfs,不留持久状态)。
|
||||
- name: 校验 SSH 配置
|
||||
ansible.builtin.command:
|
||||
argv: [incus, exec, "local:{{ incus_container }}", --, sh, -c, install -d -m 0755 /run/sshd && exec /usr/sbin/sshd -t]
|
||||
changed_when: false
|
||||
|
||||
- name: 更新运行中的 SSH 配置
|
||||
ansible.builtin.command:
|
||||
argv: [incus, exec, "local:{{ incus_container }}", --, systemctl, restart, ssh]
|
||||
when: container_sshd_write is changed
|
||||
changed_when: true
|
||||
|
||||
- name: 检查 SSH service
|
||||
ansible.builtin.command:
|
||||
argv: [incus, exec, "local:{{ incus_container }}", --, systemctl, is-active, ssh]
|
||||
register: container_ssh_active
|
||||
changed_when: false
|
||||
failed_when: container_ssh_active.rc not in [0, 3, 4]
|
||||
|
||||
- name: 检查 SSH 自启
|
||||
ansible.builtin.command:
|
||||
argv: [incus, exec, "local:{{ incus_container }}", --, systemctl, is-enabled, ssh]
|
||||
register: container_ssh_enabled
|
||||
changed_when: false
|
||||
failed_when: container_ssh_enabled.rc not in [0, 1, 3, 4]
|
||||
|
||||
- name: 启用 SSH
|
||||
ansible.builtin.command:
|
||||
argv: [incus, exec, "local:{{ incus_container }}", --, systemctl, enable, --now, ssh]
|
||||
when: container_ssh_active.rc != 0 or container_ssh_enabled.stdout != 'enabled'
|
||||
changed_when: true
|
||||
@@ -44,6 +44,37 @@ locals {
|
||||
}
|
||||
}
|
||||
|
||||
# 镜像不含 openssh-server。不设 ssh_pwauth:它会在装包前写出残缺的 sshd_config,
|
||||
# 使包自带的默认配置(UsePAM yes、Include sshd_config.d)无法落地,锁定密码的账号随即被拒。
|
||||
# 改为装包后(defer)再写 drop-in,只覆盖需要收紧的项。
|
||||
locals {
|
||||
ayatori_cloud_config = {
|
||||
manage_etc_hosts = true
|
||||
disable_root = true
|
||||
users = [{
|
||||
name = "panxiao81"
|
||||
groups = ["sudo"]
|
||||
shell = "/bin/bash"
|
||||
sudo = ["ALL=(ALL) NOPASSWD:ALL"]
|
||||
lock_passwd = true
|
||||
ssh_authorized_keys = [trimspace(file("${path.module}/../ansible/files/panxiao81.pub"))]
|
||||
}]
|
||||
# WAN 随机掉线,装包须重试。
|
||||
apt = { conf = "Acquire::Retries \"5\";" }
|
||||
package_update = true
|
||||
packages = ["openssh-server"]
|
||||
write_files = [{
|
||||
path = "/etc/ssh/sshd_config.d/60-homelab.conf"
|
||||
defer = true
|
||||
content = <<-EOT
|
||||
PasswordAuthentication no
|
||||
KbdInteractiveAuthentication no
|
||||
PermitRootLogin no
|
||||
EOT
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
resource "incus_instance" "ayatori" {
|
||||
for_each = toset(["dev", "prod"])
|
||||
name = "ayatori-${each.key}"
|
||||
@@ -73,20 +104,9 @@ resource "incus_instance" "ayatori" {
|
||||
}
|
||||
}
|
||||
})
|
||||
"cloud-init.user-data" = "#cloud-config\n${yamlencode({
|
||||
hostname = "ayatori-${each.key}"
|
||||
manage_etc_hosts = true
|
||||
ssh_pwauth = false
|
||||
disable_root = true
|
||||
users = [{
|
||||
name = "panxiao81"
|
||||
groups = ["sudo"]
|
||||
shell = "/bin/bash"
|
||||
sudo = ["ALL=(ALL) NOPASSWD:ALL"]
|
||||
lock_passwd = true
|
||||
ssh_authorized_keys = [trimspace(file("${path.module}/../ansible/files/panxiao81.pub"))]
|
||||
}]
|
||||
})}"
|
||||
"cloud-init.user-data" = "#cloud-config\n${yamlencode(merge(local.ayatori_cloud_config, {
|
||||
hostname = "ayatori-${each.key}"
|
||||
}))}"
|
||||
}
|
||||
device {
|
||||
name = "root"
|
||||
|
||||
Reference in New Issue
Block a user