综合指南:定制 AWS EC2 服务器 (SSH 隧道访问内部服务)
用于远程开发测试、托管公共 Docker 镜像 (via ELB)、运行数据库等后端服务 (via SSH Tunnel),整合安全最佳实践
目录
- 目录
- 1. 初始化 AWS EC2 服务器
- 2. 服务器安全加固 (关键步骤)
- 3. 自定义 Shell (可选,示例采用 PWSH 7)
- 4. 安装 Docker & Docker Compose
- 5. 配置 Docker Registry (ELB 公开暴露)
- 6. 配置内部服务 (通过 SSH Tunnel 访问)
- 7. 配置 NodeJS & PNPM
- 8. 配置 Python & UV
- 9. 配置 Beszel 面板 (占位符)
- 10. 配置 AWS 基础设施 (仅用于 Docker Registry)
- 11. 通过 SSH Tunnel 访问内部服务
- 12. 配置自动运维脚本
- 13. 文档、规范与日志 (参见单独文档)
- 14. 参考资料
- 15. 总结
初始化 AWS EC2 服务器
在 AWS 管理控制台中创建 EC2 服务器。
1.1. 选择 AMI
- 推荐: 最新的 Amazon Linux 2023 AMI (基于 Fedora, 长期支持)。
- 架构:根据需求选择
x86_64或arm64。
1.2. 选择实例类型
t3a.large(2 vCPU, 8 GiB 内存) 或根据需求选择。
1.3. 配置密钥对
- 创建新密钥对 (推荐
ED25519或RSA 4096)。 - 格式:选择
.pem(适用于 OpenSSH)。 - 安全存储: 下载
.pem文件后,立即保存在本地安全位置,并设置严格权限 (chmod 400 <密钥对文件名>.pemon Linux/macOS/WSL)。绝不公开或上传到代码仓库。
1.4. 网络设置
- VPC/子网: 使用默认或创建新的。
- 自动分配公有 IP: 启用 (初始连接需要)。
- 防火墙 (安全组 - 初始):
- 创建新安全组 (例如
initial-ssh-sg)。 - 入站规则: 允许 SSH (TCP 22),源选择 "我的 IP"。切勿使用
0.0.0.0/0。 - 出站规则: 允许所有 (默认)。
- 创建新安全组 (例如
1.5. 配置存储
- 大小:30 GB+。
- 类型:gp3 (推荐),启用加密。
1.6. 高级详细信息
- (通常保持默认)
1.7. 启动实例
- 确认配置无误后,点击"启动实例"。
1.8. 连接到实例 (初始)
-
等待实例状态变为
Running。 -
获取实例的 公有 IPv4 地址。
-
使用 SSH 客户端和下载的密钥对连接:
ssh -i <密钥对文件名>.pem ec2-user@<公有IP地址> -
首次连接输入
yes确认主机密钥指纹。
服务器安全加固 (关键步骤)
这是设置服务器最关键的步骤之一,必须在安装任何服务之前或刚完成初始化后立即进行!
2.1. 更新系统
sudo dnf update -y
# 如果是 Amazon Linux 2, 使用 yum:
# sudo yum update -y2.2. 创建管理员用户 (devadmin)
# 创建新用户
sudo adduser devadmin
# 设置强密码 (虽然将禁用密码登录,但设置一个总是好的)
sudo passwd devadmin
# 测试密码是否设置成功,需要输入密码进行验证,登录成功则表示成功,成功后退出。
su - devadmin
# 添加到 'wheel' 组以获取 sudo 权限 (Amazon Linux 默认需要)
sudo usermod -aG wheel devadmin
# 为 devadmin 创建 .ssh 目录并设置权限
sudo mkdir /home/devadmin/.ssh
sudo chmod 700 /home/devadmin/.ssh
sudo chown devadmin:devadmin /home/devadmin/.ssh2.3. 配置 devadmin SSH 密钥登录
-
在你的本地机器上: 找到你的 SSH 公钥文件 (通常是
~/.ssh/id_rsa.pub或~/.ssh/id_ed25519.pub)。如果还没有,使用ssh-keygen -t ed25519或ssh-keygen -t rsa -b 4096生成新的密钥对。 -
复制公钥内容: 打开公钥文件 (例如
cat ~/.ssh/id_ed25519.pub) 并复制输出的 完整 公钥字符串。 -
在 EC2 服务器上 (仍以
ec2-user登录):# 创建 authorized_keys 文件 sudo touch /home/devadmin/.ssh/authorized_keys # 设置权限 (非常重要!) sudo chmod 600 /home/devadmin/.ssh/authorized_keys sudo chown devadmin:devadmin /home/devadmin/.ssh/authorized_keys # 编辑文件并将你本地的公钥粘贴进去 sudo nano /home/devadmin/.ssh/authorized_keys # (粘贴你本地复制的公钥内容,确保没有多余的空格或换行,保存并退出 Ctrl+X, Y, Enter)
2.4. 测试 devadmin 登录
-
极其重要:不要关闭 当前的
ec2-userSSH 会话!这是你的后备通道。 -
打开一个新的本地终端窗口。
-
尝试使用
devadmin用户和 你自己的私钥 (对应你添加到服务器的公钥) 进行 SSH 登录:# 替换 <你的私钥文件> (例如 ~/.ssh/id_ed25519) 和 <公有IP地址> ssh -i <你的私钥文件> devadmin@<公有IP地址> -
如果成功登录,尝试执行
sudo whoami。如果返回root,则devadmin的密钥登录和sudo权限都配置成功。 -
故障排查: 仔细检查公钥粘贴、文件/目录权限 (
700for.ssh,600forauthorized_keys) 和所有权 (devadmin:devadmin),以及本地使用的私钥是否匹配。
2.5. SSH 服务加固 (编辑 /etc/ssh/sshd_config)
-
确认
devadmin登录成功后,使用ec2-user会话编辑 SSH 配置文件:sudo nano /etc/ssh/sshd_config -
查找并修改/确认以下关键行:
Port 22: 强烈建议修改为非标准端口 (例如Port 2222)。修改前,务必先在 AWS 安全组中为此新端口添加入站规则 (源为你的 IP)!PermitRootLogin no: 确认设置为no。PasswordAuthentication no: 确认设置为no(强制密钥认证)。PubkeyAuthentication yes: 确认设置为yes。ChallengeResponseAuthentication no: 确认设置为no。AllowUsers devadmin: 关键步骤 - 禁用ec2-user! 添加此行,明确指定只允许devadmin登录。再次确认 你的devadmin密钥登录正常工作后再添加此行!LoginGraceTime 30(可选)MaxAuthTries 3(可选)ClientAliveInterval 300(可选)ClientAliveCountMax 2(可选)
-
保存并退出编辑器 (
Ctrl+X,Y,Enter)。
2.6. 验证并重启 SSH 服务
-
在
ec2-user会话中执行:sudo sshd -t # 测试 sshd_config 配置文件语法 # 如果测试通过 (没有输出或提示 syntax OK),则重启服务 sudo systemctl restart sshd -
最终验证:
-
保持
ec2-user会话仍然打开! -
在你之前成功登录
devadmin的那个新终端窗口中,断开连接 (exit)。 -
再次尝试使用
devadmin和你的私钥登录,注意使用你修改后的端口号(如果修改了):ssh -i <你的私钥文件> -p <你的SSH端口> devadmin@<公有IP地址> -
如果能够成功登录,说明配置正确。
-
尝试使用
ec2-user登录: 打开另一个新终端,尝试用ec2-user和初始密钥对登录,应该会被拒绝连接。 -
只有在确认
devadmin可以稳定登录,并且ec2-user确实无法登录后,你才可以安全地关闭最初的ec2-userSSH 会话。
-
2.7. 配置防火墙 (firewalld)
-
现在使用
devadmin用户登录服务器进行后续操作。# 启用并启动 firewalld (如果尚未运行) sudo systemctl enable --now firewalld # 如果没有安装 firewalld,请先安装: sudo dnf install firewalld -y # 添加新的 SSH 端口 (例如 2222) sudo firewall-cmd --permanent --zone=public --add-port=<你的SSH端口>/tcp # 允许你修改后的 SSH 端口 # 移除默认 ssh (端口 22) sudo firewall-cmd --permanent --zone=public --remove-service=ssh # (可选)添加新规则允许你的 IP 访问新 SSH 端口。 # 注意:这里的firewalld 规则是针对你指定的 IP 地址的,如果限制了 IP 意味着只能在你指定的 IP 地址访问。 # 在 AWS 安全组中修改是不会影响到这里的,也就是当你的 IP 地址变化时,你就登录不了服务器了。 sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="<你的公网IP>/32" port port="<你的SSH端口>" protocol="tcp" accept' # (可选)如果需要允许 IPv6 sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv6" source address="<你的公网IPv6>/128" port port="<你的SSH端口>" protocol="tcp" accept' # 重新加载防火墙规则使其生效 sudo firewall-cmd --reload # 查看当前活动的规则 sudo firewall-cmd --list-all注意: 后续为 ELB 配置的端口(如
8080)不需要在这里开放给公网,因为流量将由 AWS 安全组控制,只允许来自 ELB。
2.8. 安装 Fail2Ban
-
(用于防止 SSH 暴力破解)
sudo dnf install fail2ban -y # 创建本地配置文件,避免升级时覆盖 sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local # 编辑本地配置文件 sudo nano /etc/fail2ban/jail.local -
在
jail.local中找到[sshd]部分。 -
确保
enabled = true。 -
关键: 将
port = ssh修改为port = <你的SSH端口>(例如port = 2222)。 -
可以按需调整
bantime,findtime,maxretry等参数。 -
保存并退出。
# 启用并启动 Fail2Ban 服务 sudo systemctl enable --now fail2ban # 检查 SSH jail 的状态 sudo fail2ban-client status sshd
完成以上步骤后,你的服务器 SSH 访问应该:
- 仅允许
devadmin用户登录。 - 仅允许使用密钥对进行认证。
- 仅能通过你指定的非标准端口访问。
- 仅能从你指定的 IP 地址访问(由 AWS 安全组和
firewalld双重控制)。 - 受到 Fail2Ban 的保护,自动阻止可疑的登录尝试。
自定义 Shell (可选,示例采用 PWSH 7)
# 添加 Microsoft RPM 仓库 (适用于 Amazon Linux 2023 / RHEL / CentOS / Fedora)
sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc
sudo sh -c 'echo -e "[packages-microsoft-com-prod]\nname=packages-microsoft-com-prod\nbaseurl=https://packages.microsoft.com/rhel/8/prod/\nenabled=1\ngpgcheck=1\ngpgkey=https://packages.microsoft.com/keys/microsoft.asc" > /etc/yum.repos.d/packages.microsoft.com-prod.repo'
# 清除缓存并安装 PowerShell
sudo dnf clean all
sudo dnf install powershell -y
# 或者从这里下载 rpm 包离线安装 https://github.com/PowerShell/PowerShell/releases/latest
# 离线安装 RPM 包:
sudo dnf install powershell-*.rpm -y
# 验证安装
pwsh --version
# 配置文件地址:`~/.config/powershell/Microsoft.PowerShell_profile.ps1`
# 显示所有文件
ls -a
# (可选) 将 pwsh 设置为默认 Shell (需要注销并重新登录才能生效)
# chsh -s /usr/bin/pwsh devadmin
# 或直接编辑 /etc/passwd 文件,找到 devadmin 的行,将 `/bin/bash` 修改为 `/usr/bin/pwsh`。安装 Docker & Docker Compose
4.1. 安装 Docker Engine
sudo dnf install docker -y4.2. 启动并启用 Docker 服务
sudo systemctl start docker
sudo systemctl enable docker4.3. 将用户添加到 docker 组
# 将你的管理员用户 (例如 devadmin) 添加到 docker 组
sudo usermod -aG docker devadmin重要: 需要注销并重新登录,或者使用 newgrp docker 命令使组成员身份生效。
# 验证 (重新登录后):
docker ps # 如果不提示权限错误,则表示成功安全警告: 将用户添加到 docker 组授予了他们与 root 等效的权限。确保只将受信任的用户添加到此组。
4.4. 安装 Docker Compose (v2)
# 最新版本通常随 Docker Engine 一起安装为插件
docker compose version如果 docker compose version 命令有效,则已安装。如果未安装或需要手动更新:
DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}
mkdir -p $DOCKER_CONFIG/cli-plugins
curl -SL https://github.com/docker/compose/releases/latest/download/docker-compose-linux-$(uname -m) -o $DOCKER_CONFIG/cli-plugins/docker-compose
chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose
# 验证
docker compose version4.5 配置镜像加速器 (可选)
在 AWS 中国区方便安全的使用海外公开容器镜像-基于 AWS ECR 的方案
配置完成后,使用 docker info 命令查看是否生效。
# 编辑 /etc/docker/daemon.json 文件
sudo nano /etc/docker/daemon.json
# 将内容写入 /etc/docker/daemon.json 文件,root 用户可以去掉 sudo
# 配置 Docker 镜像,使用多个镜像源来提高镜像下载速度
echo '{
"registry-mirrors": [
"https://docker.1ms.run",
"https://docker.1panel.live",
"https://docker.ketches.cn"
]
}' | sudo tee /etc/docker/daemon.json
# 重启 Docker 服务以使配置生效
sudo systemctl restart docker配置 Docker Registry 与 Docker Register UI (ELB 公开暴露)
目标: 搭建可通过 registry.yourdomain.com (HTTPS) 访问的 私有 仓库。
方式: Docker 容器 + Nginx 反向代理 (处理认证和转发) + ALB (处理 TLS 和公网接入)。
5.1. 创建 Nginx 相关文件
-
认证文件:
sudo dnf install httpd-tools -y sudo mkdir -p /etc/docker_registry/auth # 创建用户和密码 (替换 <registry_user>) sudo htpasswd -c /etc/docker_registry/auth/registry.passwd <registry_user> # 输入强密码 sudo chmod 644 /etc/docker_registry/auth/registry.passwd -
Nginx 配置文件 (
/etc/docker_registry/nginx.conf):# /etc/docker_registry/nginx.conf server { listen 8080; server_name ${custom docker register domain}; client_max_body_size 0; chunked_transfer_encoding on; # --- Location for Docker Registry API (v2) --- location /v2/ { # Enable Basic Authentication auth_basic "Docker Registry"; auth_basic_user_file /auth/registry.passwd; # --- CORS Configuration --- # Allow API requests originating from your UI domain. set $cors_origin "http://${custom docker register domain}"; # Adjust http/https if needed if ($http_origin ~* ^https?://docker\.wwhy\.games$) { set $cors_origin $http_origin; } add_header 'Access-Control-Allow-Origin' "$cors_origin" always; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept, X-Requested-With' always; add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Expose-Headers' 'Docker-Content-Digest' always; # Handle OPTIONS preflight requests for CORS if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' "$cors_origin" always; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept, X-Requested-With' always; add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain charset=UTF-8'; add_header 'Content-Length' 0; return 204; } # Proxy requests to the actual Docker Registry backend service proxy_pass http://docker-registry-backend:5000; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; proxy_read_timeout 900; } # --- Dedicated Health Check Location --- (For ALB) location = /healthz { # Use '= /healthz' for exact match access_log off; return 200 "OK"; add_header Content-Type text/plain; } # --- Location for Docker Registry UI (Served from Root) --- # This now handles requests for '/' and anything not matching /v2/ or /healthz location / { # No authentication needed to access the UI itself proxy_pass http://registry-ui:80; # Proxy to the UI container's root # Standard proxy headers proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; } }
5.2. 创建 docker-compose-registry.yml
在服务器上创建此文件,例如在 /home/devadmin/registry/docker-compose-registry.yml:
# /home/devadmin/registry/docker-compose-registry.yml
services:
docker-registry-backend:
image: registry:3
container_name: docker-registry-backend
restart: always
volumes:
- registry_data:/var/lib/registry
networks:
- registry-net
nginx-proxy:
image: nginx:stable-alpine
container_name: docker-registry-proxy
restart: always
ports:
# Expose 8080 on the host, accessible by the ALB
- "8080:8080" # ALB will route traffic to this port
volumes:
# Mount the updated Nginx config and the auth file directory
- /etc/docker_registry/nginx.conf:/etc/nginx/conf.d/default.conf:ro
- /etc/docker_registry/auth:/auth:ro
depends_on: # Ensure backend and UI are started before Nginx
- docker-registry-backend
- registry-ui
networks:
- registry-net
registry-ui:
image: joxit/docker-registry-ui:latest
container_name: docker-registry-ui
restart: always
# --- PORTS REMOVED --- Access is now via nginx-proxy
# ports:
# - "80:80"
# - "127.0.0.1:8081:80"
environment:
SINGLE_REGISTRY: "true" # Enclose boolean in quotes for safety
DELETE_IMAGES: "true"
SHOW_CONTENT_DIGEST: "true"
# --- REGISTRY_URL UPDATED ---
# Point to the public Nginx endpoint URL.
# IMPORTANT: Use 'https' if your ALB serves the domain via HTTPS.
REGISTRY_URL: "https://${custom docker register domain}" # Or "https://${custom docker register domain}"
REGISTRY_TITLE: "My Private Registry UI (${custom docker register domain})"
networks:
- registry-net
volumes:
registry_data:
networks:
registry-net:
driver: bridge5.3. 启动 Registry 服务
cd /home/devadmin/registry/ # 切换到包含 compose 文件的目录
docker compose -f docker-compose-registry.yml up -d下面的脚本是将 Docker Registry 和 Docker Registry UI 通过 ALB 进行访问的配置。 Docker Registry UI 需要通过 Docker Registry 的 API 来获取镜像信息。
这里只暴露了 Docker Registry Nginx 的 8080 端口,其他端口不需要暴露给 ALB。
配置内部服务 (通过 SSH Tunnel 访问)
目标: 搭建仅能通过 SSH Tunnel 访问的数据库。
方式: Docker 容器,监听本地回环地址 (127.0.0.1)。
6.1. 创建 docker-compose-internal.yml
在服务器上创建此文件,例如在 /home/devadmin/internal/docker-compose-internal.yml。
6.2. 配置 MongoDB
继续在 docker-compose-internal.yml 中添加:
# /home/devadmin/internal/docker-compose-internal.yml
services:
# ... (在 services 下继续) ...
mongodb:
image: mongo:latest
container_name: mongodb
restart: always
ports:
# **重要:** 仅绑定到 localhost
- "127.0.0.1:27017:27017"
volumes:
- mongo_data:/data/db
environment:
MONGO_INITDB_ROOT_USERNAME: <你的管理员用户名>
MONGO_INITDB_ROOT_PASSWORD: <极其复杂的密码>
# **注意:** 移除了 TLS 相关配置,因为 SSH Tunnel 提供加密
command: mongod --bind_ip 127.0.0.1,::1 --auth # 明确绑定容器内 localhost 并启用认证
networks:
- internal-services-net6.3. 配置 Redis
继续在 docker-compose-internal.yml 中添加:
# ... (在 services 下继续) ...
redis:
image: redis:latest
container_name: redis
restart: always
ports:
# **重要:** 仅绑定到 localhost
- "127.0.0.1:6379:6379"
volumes:
- redis_data:/data
# **重要:** 启用密码认证并绑定到 localhost
command: redis-server --requirepass <极其复杂的密码> --bind 127.0.0.1 # 明确绑定容器内 localhost
# **注意:** 移除了 TLS 相关配置
networks:
- internal-services-net6.4. (可选)配置 PostgreSQL
后续采用 supbase 的 PostgreSQL 作为数据库,使用 Docker Compose 部署。 这里的 PostgreSQL 仅供参考,实际使用中可以选择其他数据库或服务。
继续在 docker-compose-internal.yml 中添加:
# ... (在 services 下继续) ...
postgres:
image: postgres:latest
container_name: postgresql
restart: always
ports:
# **重要:** 仅绑定到 localhost
- "127.0.0.1:5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: <极其复杂的密码_用于postgres用户>
POSTGRES_USER: myappuser
POSTGRES_DB: myappdb
# **重要:** 修改启动参数以监听 localhost 并启用认证
command: postgres -c listen_addresses='127.0.0.1' # 明确绑定容器内 localhost
# **注意:** 移除了 TLS 相关配置
networks:
- internal-services-net
# --- 定义卷和网络 ---
volumes:
mongo_data:
redis_data:
postgres_data:
# registry_data: # 这个卷在 registry 的 compose 文件中定义
networks:
internal-services-net:
driver: bridge
# 如果 registry-ui 需要连接外部 registry-net
registry-net:
external: true # 引用在 docker-compose-registry.yml 中创建的网络6.5. 启动内部服务
cd /home/devadmin/internal/ # 切换到包含 compose 文件的目录
docker compose -f docker-compose-internal.yml up -d配置 NodeJS & PNPM
7.1. 安装 nvm
# 从 GitHub 获取最新的安装脚本并执行 (检查最新版本替换 v0.39.7)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash重要: 根据脚本输出,将 nvm 加载命令添加到你的 shell 配置文件 (~/.bashrc, ~/.zshrc, ~/.profile 或 ~/.config/powershell/profile.ps1 等)。例如,对于 bash/zsh:
# export NVM_DIR="$HOME/.nvm"
# [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
# [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion使配置生效 (重新打开终端或执行):
source ~/.bashrc # 或你的配置文件验证:
command -v nvm7.2. 安装 NodeJS
nvm install --lts # 安装最新的 LTS 版本
nvm use --lts # 使用 LTS 版本
nvm alias default lts # 设置默认版本
# 验证
node -v
npm -v7.3. 安装 PNPM
npm install -g pnpm
# 验证
pnpm -v配置 Python & UV
警告: 不要修改系统自带的 Python。使用 pyenv 管理 Python 版本。
8.1. 安装 pyenv 依赖
sudo dnf install -y gcc zlib-devel bzip2 bzip2-devel readline-devel sqlite sqlite-devel openssl-devel tk-devel libffi-devel xz-devel make findutils8.2. 安装 pyenv
curl https://pyenv.run | bash重要: 根据脚本输出,将 pyenv 初始化命令添加到你的 shell 配置文件 (~/.bashrc 等)。例如:
# export PYENV_ROOT="$HOME/.pyenv"
# command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"
# eval "$(pyenv init -)"使配置生效:
source ~/.bashrc # 或你的配置文件验证:
pyenv --version8.3. 安装 Python 版本
pyenv install 3.11.7 # 或其他所需版本
pyenv global 3.11.7 # 设置全局默认 (可选)
# 验证 (确保 pyenv 的 python 生效)
which python
python --version
pip --version8.4. 安装 UV
curl -LsSf https://astral.sh/uv/install.sh | sh根据输出,可能需要将 ~/.cargo/bin 添加到 PATH (如果尚未添加)。
source ~/.bashrc # 或你的配置文件
# 验证
uv --version8.5. 使用虚拟环境 (重要)
始终 在项目中使用虚拟环境。
cd /path/to/your/project
# 使用 uv 创建和管理
uv venv # 创建 .venv
source .venv/bin/activate # 激活 (bash/zsh)
# . .venv/bin/activate.fish # 激活 (fish)
# .venv\Scripts\activate # 激活 (Windows Cmd/PS)
uv pip install requests flask # 安装包
deactivate # 退出虚拟环境
# 或者使用标准库 venv
# python -m venv .venv
# source .venv/bin/activate
# pip install requests flask
# deactivate配置 Beszel 面板 (占位符)
- 如果这是你内部使用的工具: 参考其官方文档进行安装和配置,确保安全。
- 如果是通用面板: 考虑 Cockpit 或 Portainer。
-
Cockpit:
sudo dnf install cockpit -y sudo systemctl enable --now cockpit.socket(访问
https://<服务器IP>:9090,使用服务器用户登录) -
Portainer:
docker volume create portainer_data docker run -d -p 8000:8000 -p 9443:9443 --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:latest(访问
https://<服务器IP>:9443,首次访问设置管理员密码)
-
- 安全警告: 任何面板都增加攻击面。必须使用 HTTPS、强密码、限制访问 IP (防火墙/安全组),并保持更新。对于内部服务优先的架构,面板也应考虑通过 SSH 隧道访问 (即不在
docker run中暴露端口-p 9443:9443,而是使用-p 127.0.0.1:9443:9443并通过隧道访问localhost:9443)。
配置 AWS 基础设施 (仅用于 Docker Registry)
10.1. 配置 AWS Certificate Manager (ACM) for ELB
- 导航到 ACM。
- 请求一个 公共 证书。
- 添加域名:
registry.yourdomain.com(以及根域yourdomain.com如果需要)。 - 选择 DNS 验证 (推荐) 或电子邮件验证。
- 完成验证,等待证书状态变为 "Issued"。
10.2. 配置 ELB (Application Load Balancer)
- 导航到 EC2 -> Load Balancers -> Create Load Balancer -> Application Load Balancer。
- 基本配置: 名称 (e.g.,
registry-alb), SchemeInternet-facing, IP typeIPv4. - 网络映射: 选择 VPC,至少两个不同可用区的公共子网。
- 安全组 (用于 ALB):
- 创建新安全组 (例如
alb-registry-public-sg)。 - 入站规则:
- 允许
HTTPS(TCP 443) 来自0.0.0.0/0和::/0。 - (可选) 允许
HTTP(TCP 80) 来自0.0.0.0/0和::/0(用于重定向)。 - 不再需要 允许数据库或其他内部服务端口。
- 允许
- 分配此安全组给 ALB。
- 创建新安全组 (例如
- 监听器和路由:
- Listener 1: HTTPS 443
- Protocol:
HTTPS, Port:443. - Default action: Forward to...
- Target group: 创建新目标组。
- Type:
Instances. - Name:
tg-registry-http. - Protocol:
HTTP, Port:8080(指向 Nginx 代理)。 - VPC, Health check (HTTP
/), Register targets (选择你的 EC2 instance)。
- Type:
- 选择创建的目标组
tg-registry-http.
- Target group: 创建新目标组。
- Security policy: 推荐策略。
- Default SSL/TLS certificate: 选择
From ACM(你的registry.yourdomain.com证书)。 - 删除 所有基于 Host Header 的规则 (不再需要为 UI、DB 等创建规则)。
- Protocol:
- (可选) Listener 2: HTTP 80
- Protocol:
HTTP, Port:80. - Default action: Redirect to...
HTTPS 443.
- Protocol:
- 删除 所有用于数据库 (TLS 27017, 6379, 5432) 的监听器。
- Listener 1: HTTPS 443
- 创建 Load Balancer。
10.3. 配置安全组 (精细化 - ELB 集成 & SSH 访问)
EC2 实例的安全组 (例如 dev-server-sg)
核心原则: 仅允许来自你 IP 的 SSH 流量和来自 ALB 的 Docker Registry 流量。
| 类型 | 协议 | 端口范围 | 源 | 描述 |
|---|---|---|---|---|
| SSH | TCP | <你的SSH端口> | 你的IP地址/32 | Allow SSH from specific Admin IP |
| 自定义 TCP | TCP | 8080 | <ALB安全组ID> | Allow ALB to reach Registry Nginx Proxy |
8081 | <ALB安全组ID> | |||
27017 | <ALB安全组ID> | |||
6379 | <ALB安全组ID> | |||
5432 | <ALB安全组ID> |
ALB 的安全组 (例如 alb-registry-public-sg)
| 类型 | 协议 | 端口范围 | 源 | 描述 |
|---|---|---|---|---|
| HTTPS | TCP | 443 | 0.0.0.0/0, ::/0 | Allow public access to Docker Registry |
| HTTP | TCP | 80 | 0.0.0.0/0, ::/0 | (Optional) For HTTP->HTTPS redirect |
27017 | 0.0.0.0/0, ::/0 | |||
6379 | 0.0.0.0/0, ::/0 | |||
5432 | 0.0.0.0/0, ::/0 |
(你需要将 <你的SSH端口>, <你的IP地址/32>, <ALB安全组ID> 替换为实际值)
10.4. 配置 Route 53 (用于 Docker Registry 域名解析)
- 导航到 Route 53 -> Hosted zones -> 你的域名。
- 确保有以下记录 (或创建/修改):
- 记录 (Registry):
- Record name:
registry - Record type:
A - Alias:
On - Route traffic to: Alias to Application and Classic Load Balancer, 选择区域, 选择你的 ALB (
registry-alb). - Routing policy: Simple
- Record name:
- 记录 (Registry):
- 删除 之前为
ui.registry,mongo,redis,pg创建的指向 ALB 的 A 记录(如果存在)。
通过 SSH Tunnel 访问内部服务
11.1. 核心理念
使用你的 SSH 连接作为加密通道,将你本地机器上的端口安全地转发到 EC2 服务器上监听 127.0.0.1 的服务端口。
11.2. 前提
- 你的 SSH 连接必须是活动的。
- 你需要知道远程服务在 EC2 服务器上监听的端口 (在
docker-compose-internal.yml中定义的127.0.0.1:<端口>的那个端口)。 - 你本地需要一个未被占用的端口用于转发。
11.3. 命令格式
ssh -N -L <本地端口>:127.0.0.1:<远程服务端口> devadmin@<服务器IP> -i <你的私钥文件> [-p <SSH端口>]-N: 不执行远程命令,仅建立隧道。-L <本地端口>:127.0.0.1:<远程服务端口>: 设置本地端口转发。将本地<本地端口>的流量转发到远程服务器的127.0.0.1:<远程服务端口>。devadmin@<服务器IP>: 你的 SSH 用户名和服务器地址。-i <你的私钥文件>: 指定你的 SSH 私钥。[-p <SSH端口>]: 如果修改了 SSH 端口,需要加上-p参数。
11.4. 具体服务访问示例
保持这个 SSH Tunnel 命令在一个单独的终端窗口运行,隧道才会持续有效。
-
访问 Docker Registry UI (远程端口 8081):
-
建立隧道:
ssh -N -L 8081:127.0.0.1:8081 devadmin@<服务器IP> -i <你的私钥文件> [-p <SSH端口>] -
访问: 在你的本地浏览器中打开
http://localhost:8081。
-
-
访问 MongoDB (远程端口 27017):
-
建立隧道:
ssh -N -L 27017:127.0.0.1:27017 devadmin@<服务器IP> -i <你的私钥文件> [-p <SSH端口>] -
连接 (使用
mongosh或其他客户端):# 使用你设置的用户名和密码 mongosh "mongodb://<你的管理员用户名>:<极其复杂的密码>@localhost:27017/?authSource=admin"
-
-
访问 Redis (远程端口 6379):
-
建立隧道:
ssh -N -L 6379:127.0.0.1:6379 devadmin@<服务器IP> -i <你的私钥文件> [-p <SSH端口>] -
连接 (使用
redis-cli):redis-cli -h localhost -p 6379 -a <极其复杂的密码> # 输入 ping,应返回 PONG
-
-
访问 PostgreSQL (远程端口 5432):
-
建立隧道:
ssh -N -L 5432:127.0.0.1:5432 devadmin@<服务器IP> -i <你的私钥文件> [-p <SSH端口>] -
连接 (使用
psql):# 使用 postgres 超级用户 psql -h localhost -p 5432 -U postgres -W # 输入 postgres 用户的密码 # 使用应用用户连接到应用数据库 (需要先设置密码) # psql -h localhost -p 5432 -U myappuser -d myappdb -W # 输入 myappuser 的密码
-
11.5. 安全提示
- 保护好你的 SSH 私钥!
- 确保你的 SSH 连接本身是安全的(已遵循安全加固步骤)。
- 当你不再需要访问时,记得关闭 SSH Tunnel 终端窗口 (通常按
Ctrl+C)。
配置自动运维脚本
目标: 自动化常见维护任务。
工具: cron, Shell 脚本, AWS CLI
12.1. 自动系统更新
-
创建脚本
/usr/local/sbin/auto_update.sh:#!/bin/bash LOG_FILE="/var/log/auto_update.log" echo "Starting system update on $(date)" >> $LOG_FILE # 使用 dnf (Amazon Linux 2023) sudo dnf update -y >> $LOG_FILE 2>&1 # 如果是 Amazon Linux 2, 使用 yum: # sudo yum update -y >> $LOG_FILE 2>&1 echo "Finished system update on $(date)" >> $LOG_FILE exit 0 -
设置权限:
sudo chmod +x /usr/local/sbin/auto_update.sh -
添加 Cron 任务 (
sudo crontab -e):# 每周日凌晨 3:30 执行自动更新 30 3 * * 0 /usr/local/sbin/auto_update.sh
12.2. Docker 资源清理
-
创建脚本
/usr/local/sbin/docker_cleanup.sh:#!/bin/bash LOG_FILE="/var/log/docker_cleanup.log" echo "Starting Docker cleanup on $(date)" >> $LOG_FILE # 清理停止的容器 docker container prune -f >> $LOG_FILE 2>&1 # 清理未使用的网络 docker network prune -f >> $LOG_FILE 2>&1 # 清理悬空的镜像 (dangling images) docker image prune -f >> $LOG_FILE 2>&1 # 清理未使用的卷 (谨慎使用,确保没有重要数据) # docker volume prune -f >> $LOG_FILE 2>&1 # 清理超过 30 天未使用的所有镜像 (包括非悬空镜像) docker image prune -a -f --filter "until=720h" >> $LOG_FILE 2>&1 echo "Finished Docker cleanup on $(date)" >> $LOG_FILE exit 0 -
设置权限:
sudo chmod +x /usr/local/sbin/docker_cleanup.sh -
添加 Cron 任务 (
sudo crontab -e):# 每周日凌晨 4:00 执行 Docker 清理 0 4 * * 0 /usr/local/sbin/docker_cleanup.sh
12.3. 数据库备份 (示例)
- (参考
1initialize-aws-ec2-based-server.mdx中的 PostgreSQL 备份脚本示例) - 确保备份脚本使用
127.0.0.1或 Docker 容器名连接数据库。 - 将备份文件安全地传输到 S3 或其他存储 (使用 AWS CLI 或
rclone等)。 - 为 MongoDB (
mongodump) 和 Redis (RDB/AOF 文件复制) 创建类似的备份脚本。 - 使用
cron定期执行备份脚本。
12.4. 其他考虑
- 日志轮转: 检查并配置
logrotate(/etc/logrotate.conf和/etc/logrotate.d/) 以管理日志文件大小。 - 监控和告警: 使用 AWS CloudWatch 监控 EC2 指标 (CPU, 内存, 网络) 和日志。设置告警。
- 配置管理: 考虑使用 Ansible, Terraform 或 AWS Systems Manager 等工具来自动化和管理服务器配置。
参考资料
- AWS 文档 (EC2, AL2023, Security Groups, ALB, ACM, Route 53, IAM, Secrets Manager, Parameter Store, CLI)
- Docker 文档 (Engine, Compose, Registry, Registry UI)
- 服务文档 (Nginx, Fail2Ban, firewalld, nvm, pyenv, uv, MongoDB, Redis, PostgreSQL)
- 安全最佳实践 (CIS Benchmarks, OWASP Top 10)
- SSH 端口转发/隧道教程
总结
此配置方案通过 ELB 安全地暴露了需要公共访问的 Docker Registry,同时将其他内部服务(UI、数据库)隔离在服务器内部,仅允许通过加密的 SSH Tunnel 进行访问。这显著降低了攻击面,提高了内部服务的安全性。
管理 SSH 访问权限和保护私钥成为此架构的关键安全点。