feat(deploy): add docker
This commit is contained in:
42
deploy/README.md
Normal file
42
deploy/README.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# iam-service 部署(仅核心服务)
|
||||||
|
|
||||||
|
本目录仅包含 **iam-service** 的部署配置,明确排除以下组件:
|
||||||
|
- PostgreSQL(不在本目录内提供部署/启动)
|
||||||
|
- Redis(不在本目录内提供部署/启动)
|
||||||
|
- iam-front(不在本目录内提供部署/启动)
|
||||||
|
|
||||||
|
## 环境变量(唯一真实来源)
|
||||||
|
- 唯一来源:`iam-service/.env`
|
||||||
|
- 部署脚本与 docker-compose 均以该文件作为环境变量来源(包括端口映射所需的 `${PORT}` 插值)。
|
||||||
|
|
||||||
|
## 密钥文件验证(以 data 目录为准)
|
||||||
|
- `iam-service/data/jwt_private_key.pem`
|
||||||
|
- `iam-service/data/jwt_public_key.pem`
|
||||||
|
|
||||||
|
若 `.env` 中显式设置了 `JWT_PRIVATE_KEY_PEM` / `JWT_PUBLIC_KEY_PEM`,校验脚本会要求其内容与上述文件内容一致;若未设置,则要求上述文件存在且非空。
|
||||||
|
|
||||||
|
## 部署文件结构
|
||||||
|
- `deploy/validate-env.sh`:环境变量与密钥文件校验
|
||||||
|
- `deploy/docker/Dockerfile`:iam-service 镜像构建
|
||||||
|
- `deploy/docker/docker-compose.yml`:仅定义 iam-service 服务
|
||||||
|
- `deploy/docker/start.sh`:启动(会先执行校验脚本)
|
||||||
|
- `deploy/docker/stop.sh`:停止
|
||||||
|
|
||||||
|
## 启动/停止
|
||||||
|
|
||||||
|
启动:
|
||||||
|
```bash
|
||||||
|
bash deploy/docker/start.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
停止:
|
||||||
|
```bash
|
||||||
|
bash deploy/docker/stop.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 外部依赖(由你自行提供)
|
||||||
|
iam-service 启动仍需要以下外部服务地址(来自 `.env`):
|
||||||
|
- `DATABASE_URL`:指向已存在的 PostgreSQL 实例
|
||||||
|
- `REDIS_URL`:指向已存在的 Redis 实例
|
||||||
|
|
||||||
|
注意:若通过 Docker 部署,`.env` 中的 `localhost/127.0.0.1` 指向的是容器自身,不是宿主机。此时请将地址改为宿主机可达 IP 或网关域名(例如 `host.docker.internal`,视你的 Docker 环境而定)。
|
||||||
51
deploy/docker/Dockerfile
Normal file
51
deploy/docker/Dockerfile
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
FROM rust:1.91-slim-bookworm AS builder
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# 官方 Rust 镜像中 CARGO_HOME = /usr/local/cargo
|
||||||
|
RUN echo '[source.crates-io]' > $CARGO_HOME/config.toml \
|
||||||
|
&& echo 'replace-with = "rsproxy-sparse"' >> $CARGO_HOME/config.toml \
|
||||||
|
&& echo '[source.rsproxy]' >> $CARGO_HOME/config.toml \
|
||||||
|
&& echo 'registry = "https://rsproxy.cn/crates.io-index"' >> $CARGO_HOME/config.toml \
|
||||||
|
&& echo '[source.rsproxy-sparse]' >> $CARGO_HOME/config.toml \
|
||||||
|
&& echo 'registry = "sparse+https://rsproxy.cn/index/"' >> $CARGO_HOME/config.toml \
|
||||||
|
&& echo '[registries.rsproxy]' >> $CARGO_HOME/config.toml \
|
||||||
|
&& echo 'index = "https://rsproxy.cn/crates.io-index"' >> $CARGO_HOME/config.toml
|
||||||
|
|
||||||
|
# 验证一下文件是否真的存在(构建时会在 log 打印出来,让你放心)
|
||||||
|
RUN cat $CARGO_HOME/config.toml
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends ca-certificates pkg-config libssl-dev git openssh-client \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY Cargo.toml Cargo.lock ./
|
||||||
|
COPY .cargo ./.cargo
|
||||||
|
RUN mkdir -p src && echo "fn main() {}" > src/main.rs
|
||||||
|
RUN cargo build --release --locked
|
||||||
|
|
||||||
|
COPY src ./src
|
||||||
|
COPY docs ./docs
|
||||||
|
RUN touch src/main.rs
|
||||||
|
RUN cargo build --release --locked
|
||||||
|
|
||||||
|
FROM debian:bookworm-slim AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources \
|
||||||
|
&& sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends ca-certificates libssl3 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN groupadd --system --gid 10001 iam \
|
||||||
|
&& useradd --system --uid 10001 --gid 10001 --no-create-home --shell /usr/sbin/nologin iam \
|
||||||
|
&& mkdir -p /app/log /app/data \
|
||||||
|
&& chown -R iam:iam /app/log
|
||||||
|
|
||||||
|
ENV PORT=5020
|
||||||
|
EXPOSE 5020
|
||||||
|
|
||||||
|
COPY --from=builder /usr/src/app/target/release/iam-service /app/iam-service
|
||||||
|
USER iam
|
||||||
|
CMD ["/app/iam-service"]
|
||||||
12
deploy/docker/docker-compose.yml
Normal file
12
deploy/docker/docker-compose.yml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
services:
|
||||||
|
iam-service:
|
||||||
|
build:
|
||||||
|
context: ../..
|
||||||
|
dockerfile: deploy/docker/Dockerfile
|
||||||
|
env_file:
|
||||||
|
- ../../.env
|
||||||
|
ports:
|
||||||
|
- "${PORT}:${PORT}"
|
||||||
|
volumes:
|
||||||
|
- ../../data:/app/data:ro
|
||||||
|
- ../../log:/app/log
|
||||||
16
deploy/docker/start.sh
Executable file
16
deploy/docker/start.sh
Executable file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
cd "${SCRIPT_DIR}"
|
||||||
|
|
||||||
|
# 1. Validate environment
|
||||||
|
export DEPLOY_TARGET=docker
|
||||||
|
bash ../validate-env.sh
|
||||||
|
|
||||||
|
# 2. Start
|
||||||
|
echo "Starting iam-service..."
|
||||||
|
# Explicit project name to avoid conflict
|
||||||
|
docker compose --env-file ../../.env -p iam-service up -d --build
|
||||||
|
|
||||||
|
echo "iam-service is running."
|
||||||
10
deploy/docker/stop.sh
Executable file
10
deploy/docker/stop.sh
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
cd "${SCRIPT_DIR}"
|
||||||
|
|
||||||
|
echo "Stopping iam-service..."
|
||||||
|
docker compose --env-file ../../.env -p iam-service down
|
||||||
|
|
||||||
|
echo "iam-service stopped."
|
||||||
85
deploy/validate-env.sh
Executable file
85
deploy/validate-env.sh
Executable file
@@ -0,0 +1,85 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||||
|
|
||||||
|
ENV_FILE="${ROOT_DIR}/.env"
|
||||||
|
DATA_DIR="${ROOT_DIR}/data"
|
||||||
|
|
||||||
|
if [[ ! -f "${ENV_FILE}" ]]; then
|
||||||
|
echo "Missing .env: ${ENV_FILE}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
require_key() {
|
||||||
|
local key="$1"
|
||||||
|
if ! grep -Eq "^[[:space:]]*${key}=" "${ENV_FILE}"; then
|
||||||
|
echo "Missing required env var in .env: ${key}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
require_key "DATABASE_URL"
|
||||||
|
require_key "REDIS_URL"
|
||||||
|
require_key "JWT_SECRET"
|
||||||
|
require_key "AUTH_CODE_JWT_SECRET"
|
||||||
|
|
||||||
|
if grep -Eq "^[[:space:]]*PORT=" "${ENV_FILE}"; then
|
||||||
|
port="$(grep -E "^[[:space:]]*PORT=" "${ENV_FILE}" | tail -n 1 | cut -d= -f2- | tr -d '\r' | tr -d '"')"
|
||||||
|
if [[ -n "${port}" && ! "${port}" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "PORT must be a number in .env (got: ${port})"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${DEPLOY_TARGET:-}" == "docker" ]]; then
|
||||||
|
redis_url="$(grep -E "^[[:space:]]*REDIS_URL=" "${ENV_FILE}" | tail -n 1 | cut -d= -f2- | tr -d '\r')"
|
||||||
|
if [[ "${redis_url}" == redis://localhost* || "${redis_url}" == redis://127.0.0.1* ]]; then
|
||||||
|
echo "REDIS_URL cannot use localhost/127.0.0.1 for docker deployment: ${redis_url}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
db_url="$(grep -E "^[[:space:]]*DATABASE_URL=" "${ENV_FILE}" | tail -n 1 | cut -d= -f2- | tr -d '\r')"
|
||||||
|
if [[ "${db_url}" == *://localhost* || "${db_url}" == *://127.0.0.1* ]]; then
|
||||||
|
echo "DATABASE_URL cannot use localhost/127.0.0.1 for docker deployment: ${db_url}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "${DATA_DIR}/jwt_private_key.pem" ]]; then
|
||||||
|
echo "Missing key file: ${DATA_DIR}/jwt_private_key.pem"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ ! -f "${DATA_DIR}/jwt_public_key.pem" ]]; then
|
||||||
|
echo "Missing key file: ${DATA_DIR}/jwt_public_key.pem"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -s "${DATA_DIR}/jwt_private_key.pem" ]]; then
|
||||||
|
echo "Empty key file: ${DATA_DIR}/jwt_private_key.pem"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ ! -s "${DATA_DIR}/jwt_public_key.pem" ]]; then
|
||||||
|
echo "Empty key file: ${DATA_DIR}/jwt_public_key.pem"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -Eq "^[[:space:]]*JWT_PRIVATE_KEY_PEM=" "${ENV_FILE}" || grep -Eq "^[[:space:]]*JWT_PUBLIC_KEY_PEM=" "${ENV_FILE}"; then
|
||||||
|
env_priv="$(grep -E "^[[:space:]]*JWT_PRIVATE_KEY_PEM=" "${ENV_FILE}" | tail -n 1 | cut -d= -f2- | tr -d '\r')"
|
||||||
|
env_pub="$(grep -E "^[[:space:]]*JWT_PUBLIC_KEY_PEM=" "${ENV_FILE}" | tail -n 1 | cut -d= -f2- | tr -d '\r')"
|
||||||
|
|
||||||
|
file_priv="$(cat "${DATA_DIR}/jwt_private_key.pem")"
|
||||||
|
file_pub="$(cat "${DATA_DIR}/jwt_public_key.pem")"
|
||||||
|
|
||||||
|
if [[ -n "${env_priv}" && "${env_priv}" != "${file_priv}" ]]; then
|
||||||
|
echo "JWT_PRIVATE_KEY_PEM in .env does not match data/jwt_private_key.pem"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ -n "${env_pub}" && "${env_pub}" != "${file_pub}" ]]; then
|
||||||
|
echo "JWT_PUBLIC_KEY_PEM in .env does not match data/jwt_public_key.pem"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "iam-service .env validation OK"
|
||||||
Reference in New Issue
Block a user