logoDocs

综合指南:定制 AWS EC2 服务器 (SSH 隧道访问内部服务)

用于远程开发测试、托管公共 Docker 镜像 (via ELB)、运行数据库等后端服务 (via SSH Tunnel),整合安全最佳实践

目录


初始化 AWS EC2 服务器

在 AWS 管理控制台中创建 EC2 服务器。

1.1. 选择 AMI

  • 推荐: 最新的 Amazon Linux 2023 AMI (基于 Fedora, 长期支持)。
  • 架构:根据需求选择 x86_64arm64

1.2. 选择实例类型

  • t3a.large (2 vCPU, 8 GiB 内存) 或根据需求选择。

1.3. 配置密钥对

  • 创建新密钥对 (推荐 ED25519RSA 4096)。
  • 格式:选择 .pem (适用于 OpenSSH)。
  • 安全存储: 下载 .pem 文件后,立即保存在本地安全位置,并设置严格权限 (chmod 400 <密钥对文件名>.pem on 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 -y

2.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/.ssh

2.3. 配置 devadmin SSH 密钥登录

  • 在你的本地机器上: 找到你的 SSH 公钥文件 (通常是 ~/.ssh/id_rsa.pub~/.ssh/id_ed25519.pub)。如果还没有,使用 ssh-keygen -t ed25519ssh-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-user SSH 会话!这是你的后备通道。

  • 打开一个新的本地终端窗口。

  • 尝试使用 devadmin 用户和 你自己的私钥 (对应你添加到服务器的公钥) 进行 SSH 登录:

    # 替换 <你的私钥文件> (例如 ~/.ssh/id_ed25519) 和 <公有IP地址>
    ssh -i <你的私钥文> devadmin@<公有IP地>
  • 如果成功登录,尝试执行 sudo whoami。如果返回 root,则 devadmin 的密钥登录和 sudo 权限都配置成功。

  • 故障排查: 仔细检查公钥粘贴、文件/目录权限 (700 for .ssh, 600 for authorized_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-user SSH 会话。

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`。

Powershell 7 Config File


安装 Docker & Docker Compose

4.1. 安装 Docker Engine

sudo dnf install docker -y

4.2. 启动并启用 Docker 服务

sudo systemctl start docker
sudo systemctl enable docker

4.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 version

4.5 配置镜像加速器 (可选)

在 AWS 中国区方便安全的使用海外公开容器镜像-基于 AWS ECR 的方案

Docker Hub 镜像加速器

国内 Docker 服务状态 & 镜像加速监控

配置完成后,使用 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: bridge

5.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。

完整的 docker-registry 配置

配套的 nginx 配置


配置内部服务 (通过 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-net

6.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-net

6.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 nvm

7.2. 安装 NodeJS

nvm install --lts # 安装最新的 LTS 版本
nvm use --lts     # 使用 LTS 版本
nvm alias default lts # 设置默认版本
# 验证
node -v
npm -v

7.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 findutils

8.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 --version

8.3. 安装 Python 版本

pyenv install 3.11.7 # 或其他所需版本
pyenv global 3.11.7 # 设置全局默认 (可选)
# 验证 (确保 pyenv 的 python 生效)
which python
python --version
pip --version

8.4. 安装 UV

curl -LsSf https://astral.sh/uv/install.sh | sh

根据输出,可能需要将 ~/.cargo/bin 添加到 PATH (如果尚未添加)。

source ~/.bashrc # 或你的配置文件
# 验证
uv --version

8.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 面板 (占位符)

  • 如果这是你内部使用的工具: 参考其官方文档进行安装和配置,确保安全。
  • 如果是通用面板: 考虑 CockpitPortainer
    • 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

  1. 导航到 ACM。
  2. 请求一个 公共 证书。
  3. 添加域名:registry.yourdomain.com (以及根域 yourdomain.com 如果需要)。
  4. 选择 DNS 验证 (推荐) 或电子邮件验证。
  5. 完成验证,等待证书状态变为 "Issued"。

10.2. 配置 ELB (Application Load Balancer)

  1. 导航到 EC2 -> Load Balancers -> Create Load Balancer -> Application Load Balancer。
  2. 基本配置: 名称 (e.g., registry-alb), Scheme Internet-facing, IP type IPv4.
  3. 网络映射: 选择 VPC,至少两个不同可用区的公共子网。
  4. 安全组 (用于 ALB):
    • 创建新安全组 (例如 alb-registry-public-sg)。
    • 入站规则:
      • 允许 HTTPS (TCP 443) 来自 0.0.0.0/0::/0
      • (可选) 允许 HTTP (TCP 80) 来自 0.0.0.0/0::/0 (用于重定向)。
      • 不再需要 允许数据库或其他内部服务端口。
    • 分配此安全组给 ALB。
  5. 监听器和路由:
    • 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)。
        • 选择创建的目标组 tg-registry-http.
      • Security policy: 推荐策略。
      • Default SSL/TLS certificate: 选择 From ACM (你的 registry.yourdomain.com 证书)。
      • 删除 所有基于 Host Header 的规则 (不再需要为 UI、DB 等创建规则)。
    • (可选) Listener 2: HTTP 80
      • Protocol: HTTP, Port: 80.
      • Default action: Redirect to... HTTPS 443.
    • 删除 所有用于数据库 (TLS 27017, 6379, 5432) 的监听器。
  6. 创建 Load Balancer。

10.3. 配置安全组 (精细化 - ELB 集成 & SSH 访问)

EC2 实例的安全组 (例如 dev-server-sg)

核心原则: 仅允许来自你 IP 的 SSH 流量和来自 ALB 的 Docker Registry 流量。

类型协议端口范围描述
SSHTCP<你的SSH端口>你的IP地址/32Allow SSH from specific Admin IP
自定义 TCPTCP8080<ALB安全组ID>Allow ALB to reach Registry Nginx Proxy
自定义 TCPTCP8081<ALB安全组ID>Removed: UI now internal via tunnel
自定义 TCPTCP27017<ALB安全组ID>Removed: MongoDB now internal via tunnel
自定义 TCPTCP6379<ALB安全组ID>Removed: Redis now internal via tunnel
自定义 TCPTCP5432<ALB安全组ID>Removed: Postgres now internal via tunnel

ALB 的安全组 (例如 alb-registry-public-sg)

类型协议端口范围描述
HTTPSTCP4430.0.0.0/0, ::/0Allow public access to Docker Registry
HTTPTCP800.0.0.0/0, ::/0(Optional) For HTTP->HTTPS redirect
自定义 TCPTCP270170.0.0.0/0, ::/0Removed: No longer exposing DB via ALB
自定义 TCPTCP63790.0.0.0/0, ::/0Removed: No longer exposing DB via ALB
自定义 TCPTCP54320.0.0.0/0, ::/0Removed: No longer exposing DB via ALB

(你需要将 <你的SSH端口>, <你的IP地址/32>, <ALB安全组ID> 替换为实际值)

10.4. 配置 Route 53 (用于 Docker Registry 域名解析)

  1. 导航到 Route 53 -> Hosted zones -> 你的域名。
  2. 确保有以下记录 (或创建/修改):
    • 记录 (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
  3. 删除 之前为 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 命令在一个单独的终端窗口运行,隧道才会持续有效。

  1. 访问 Docker Registry UI (远程端口 8081):

    • 建立隧道:

      ssh -N -L 8081:127.0.0.1:8081 devadmin@<服务器IP> -i <你的私钥文> [-p <SSH端>]
    • 访问: 在你的本地浏览器中打开 http://localhost:8081

  2. 访问 MongoDB (远程端口 27017):

    • 建立隧道:

      ssh -N -L 27017:127.0.0.1:27017 devadmin@<服务器IP> -i <你的私钥文> [-p <SSH端>]
    • 连接 (使用 mongosh 或其他客户端):

      # 使用你设置的用户名和密码
      mongosh "mongodb://<你的管理员用户名>:<极其复杂的密码>@localhost:27017/?authSource=admin"
  3. 访问 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
  4. 访问 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 访问权限和保护私钥成为此架构的关键安全点。

On this page

目录
初始化 AWS EC2 服务器
1.1. 选择 AMI
1.2. 选择实例类型
1.3. 配置密钥对
1.4. 网络设置
1.5. 配置存储
1.6. 高级详细信息
1.7. 启动实例
1.8. 连接到实例 (初始)
服务器安全加固 (关键步骤)
2.1. 更新系统
2.2. 创建管理员用户 (devadmin)
2.3. 配置 devadmin SSH 密钥登录
2.4. 测试 devadmin 登录
2.5. SSH 服务加固 (编辑 /etc/ssh/sshd_config)
2.6. 验证并重启 SSH 服务
2.7. 配置防火墙 (firewalld)
2.8. 安装 Fail2Ban
自定义 Shell (可选,示例采用 PWSH 7)
安装 Docker & Docker Compose
4.1. 安装 Docker Engine
4.2. 启动并启用 Docker 服务
4.3. 将用户添加到 docker
4.4. 安装 Docker Compose (v2)
4.5 配置镜像加速器 (可选)
配置 Docker Registry 与 Docker Register UI (ELB 公开暴露)
5.1. 创建 Nginx 相关文件
5.2. 创建 docker-compose-registry.yml
5.3. 启动 Registry 服务
配置内部服务 (通过 SSH Tunnel 访问)
6.1. 创建 docker-compose-internal.yml
6.2. 配置 MongoDB
6.3. 配置 Redis
6.4. (可选)配置 PostgreSQL
6.5. 启动内部服务
配置 NodeJS & PNPM
7.1. 安装 nvm
7.2. 安装 NodeJS
7.3. 安装 PNPM
配置 Python & UV
8.1. 安装 pyenv 依赖
8.2. 安装 pyenv
8.3. 安装 Python 版本
8.4. 安装 UV
8.5. 使用虚拟环境 (重要)
配置 Beszel 面板 (占位符)
配置 AWS 基础设施 (仅用于 Docker Registry)
10.1. 配置 AWS Certificate Manager (ACM) for ELB
10.2. 配置 ELB (Application Load Balancer)
10.3. 配置安全组 (精细化 - ELB 集成 & SSH 访问)
10.4. 配置 Route 53 (用于 Docker Registry 域名解析)
通过 SSH Tunnel 访问内部服务
11.1. 核心理念
11.2. 前提
11.3. 命令格式
11.4. 具体服务访问示例
11.5. 安全提示
配置自动运维脚本
12.1. 自动系统更新
12.2. Docker 资源清理
12.3. 数据库备份 (示例)
12.4. 其他考虑
文档、规范与日志
参考资料
总结