deployment with GitHub Actions, Stunnel for TLS database connections, and dynamic configuration injection.
This commit is contained in:
parent
7f6fe07f7f
commit
53f19c379f
49
.github/workflows/deploy.yml
vendored
Normal file
49
.github/workflows/deploy.yml
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
name: Build and Deploy to Cloud Run
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
|
||||
env:
|
||||
PROJECT_ID: your-project-id
|
||||
REGION: asia-northeast1 # 既然你在日本,建议选东京或大阪
|
||||
SERVICE_NAME: my-node-app
|
||||
REPOSITORY: my-repo
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: 'read'
|
||||
id-token: 'write' # WIF 身份验证必填
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# 1. 身份验证 (使用 Workload Identity Federation)
|
||||
- name: Google Auth
|
||||
uses: google-github-actions/auth@v2
|
||||
with:
|
||||
workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider'
|
||||
service_account: 'my-service-account@your-project-id.iam.gserviceaccount.com'
|
||||
|
||||
# 2. 配置 Docker 认证
|
||||
- name: Docker Auth
|
||||
run: |-
|
||||
gcloud auth configure-docker ${{ env.REGION }}-docker.pkg.dev --quiet
|
||||
|
||||
# 3. 构建并推送镜像
|
||||
- name: Build and Push Container
|
||||
run: |-
|
||||
DOCKER_TAG="${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.SERVICE_NAME }}:${{ github.sha }}"
|
||||
docker build -t $DOCKER_TAG .
|
||||
docker push $DOCKER_TAG
|
||||
|
||||
# 4. 部署到 Cloud Run
|
||||
- name: Deploy to Cloud Run
|
||||
uses: google-github-actions/deploy-cloudrun@v2
|
||||
with:
|
||||
service: ${{ env.SERVICE_NAME }}
|
||||
region: ${{ env.REGION }}
|
||||
image: ${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.SERVICE_NAME }}:${{ github.sha }}
|
||||
@ -23,11 +23,14 @@ FROM ubuntu:24.04
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
&& apt-get install -y --no-install-recommends ca-certificates stunnel4 gettext-base \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /var/run/stunnel \
|
||||
&& chown -R nobody:nogroup /var/run/stunnel
|
||||
|
||||
COPY --from=builder /src/account /usr/local/bin/account
|
||||
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
COPY config /app/config
|
||||
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
|
||||
43
docs/google-cloud-run-howto.md
Normal file
43
docs/google-cloud-run-howto.md
Normal file
@ -0,0 +1,43 @@
|
||||
# Google Cloud Run Configuration Guide
|
||||
|
||||
## Required Configuration
|
||||
|
||||
Before the `deploy.yml` workflow can run successfully, you must configure the following:
|
||||
|
||||
### 1. Update `deploy.yml` Environment Variables
|
||||
|
||||
Open the workflow file `.github/workflows/deploy.yml` and update the `env` section:
|
||||
- `PROJECT_ID`: Set to your Google Cloud Project ID.
|
||||
- `REGION`: Update if you want to use a region other than `asia-northeast1` (e.g., `asia-northeast1` for Tokyo).
|
||||
- `SERVICE_NAME`: Confirm the Cloud Run service name.
|
||||
- `REPOSITORY`: Confirm the Artifact Registry repository name.
|
||||
|
||||
### 2. Configure Workload Identity Federation (WIF)
|
||||
|
||||
You need to replace the placeholders in the `Google Auth` step or use GitHub Secrets (Recommended).
|
||||
|
||||
**Recommended Approach:**
|
||||
|
||||
1. Go to your GitHub Repository -> **Settings** -> **Secrets and variables** -> **Actions**.
|
||||
2. Create a new Repository Secret named `WIF_PROVIDER`.
|
||||
- Value should be the full provider path, e.g., `projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider`.
|
||||
3. Create a new Repository Secret named `WIF_SERVICE_ACCOUNT` with your service account email, e.g., `my-service-account@your-project-id.iam.gserviceaccount.com`.
|
||||
|
||||
Then update the workflow file (`.github/workflows/deploy.yml`) to use these secrets:
|
||||
|
||||
```yaml
|
||||
# 1. 身份验证 (使用 Workload Identity Federation)
|
||||
- name: Google Auth
|
||||
uses: google-github-actions/auth@v2
|
||||
with:
|
||||
workload_identity_provider: ${{ secrets.WIF_PROVIDER }}
|
||||
service_account: ${{ secrets.WIF_SERVICE_ACCOUNT }}
|
||||
```
|
||||
|
||||
### 3. Grant Permissions
|
||||
|
||||
Ensure your Service Account has the following IAM roles in Google Cloud:
|
||||
|
||||
- `Artifact Registry Writer` (roles/artifactregistry.writer): To push container images.
|
||||
- `Cloud Run Admin` (roles/run.admin): To deploy new revisions.
|
||||
- `Service Account User` (roles/iam.serviceAccountUser): To act as the service account during deployment.
|
||||
137
entrypoint.sh
137
entrypoint.sh
@ -1,15 +1,116 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
CONFIG_FILE="${CONFIG_PATH:-/etc/xcontrol/account.yaml}"
|
||||
DEFAULT_CONFIG="/etc/xcontrol/account.yaml"
|
||||
mkdir -p "$(dirname "${CONFIG_FILE}")"
|
||||
# -----------------------------------------------------------------------------
|
||||
# Stunnel Configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
if [ ! -f "${CONFIG_FILE}" ]; then
|
||||
cp "${DEFAULT_CONFIG}" "${CONFIG_FILE}"
|
||||
if [ -n "${DB_TLS_HOST:-}" ] && [ -n "${DB_TLS_PORT:-}" ]; then
|
||||
echo "Configuring Stunnel..."
|
||||
|
||||
STUNNEL_CONF="/etc/stunnel/stunnel.conf"
|
||||
mkdir -p /etc/stunnel
|
||||
|
||||
# Write certificate files from env vars if present
|
||||
if [ -n "${DB_CA:-}" ]; then
|
||||
echo "${DB_CA}" > /etc/stunnel/ca.pem
|
||||
fi
|
||||
if [ -n "${DB_CERT:-}" ]; then
|
||||
echo "${DB_CERT}" > /etc/stunnel/cert.pem
|
||||
fi
|
||||
if [ -n "${DB_KEY:-}" ]; then
|
||||
echo "${DB_KEY}" > /etc/stunnel/key.pem
|
||||
fi
|
||||
|
||||
# Generate stunnel.conf
|
||||
cat > "${STUNNEL_CONF}" <<EOF
|
||||
foreground = no
|
||||
pid = /var/run/stunnel/stunnel.pid
|
||||
socket = l:TCP_NODELAY=1
|
||||
socket = r:TCP_NODELAY=1
|
||||
debug = 5
|
||||
output = /var/log/stunnel.log
|
||||
|
||||
[postgres]
|
||||
client = yes
|
||||
accept = 127.0.0.1:5432
|
||||
connect = ${DB_TLS_HOST}:${DB_TLS_PORT}
|
||||
verify = 2
|
||||
EOF
|
||||
|
||||
if [ -f "/etc/stunnel/ca.pem" ]; then
|
||||
echo "CAfile = /etc/stunnel/ca.pem" >> "${STUNNEL_CONF}"
|
||||
fi
|
||||
if [ -f "/etc/stunnel/cert.pem" ] && [ -f "/etc/stunnel/key.pem" ]; then
|
||||
echo "cert = /etc/stunnel/cert.pem" >> "${STUNNEL_CONF}"
|
||||
echo "key = /etc/stunnel/key.pem" >> "${STUNNEL_CONF}"
|
||||
fi
|
||||
|
||||
echo "Starting Stunnel..."
|
||||
stunnel "${STUNNEL_CONF}"
|
||||
|
||||
# Wait for stunnel to be up (simple check)
|
||||
for i in {1..10}; do
|
||||
if nc -z 127.0.0.1 5432; then
|
||||
echo "Stunnel is up!"
|
||||
break
|
||||
fi
|
||||
echo "Waiting for Stunnel..."
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# App Configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
CONFIG_FILE="${CONFIG_PATH:-/etc/xcontrol/account.yaml}"
|
||||
DEFAULT_CONFIG="/app/config/account.yaml" # Note: Adjusted path as config is likely copied to /app/config in Docker or we assume standard location
|
||||
|
||||
# If config not in /etc, maybe we need to copy it there or use what's available
|
||||
# The original script assumed /etc/xcontrol/account.yaml or copied from DEFAULT_CONFIG
|
||||
# But where is DEFAULT_CONFIG? In Dockerfile:
|
||||
# COPY --from=builder /src/account /usr/local/bin/account -> Only binary
|
||||
# We need to make sure config file exists.
|
||||
# Looking at Dockerfile again:
|
||||
# It assumes the app might have config embedded or user mounts it?
|
||||
# Wait, original entrypoint had: DEFAULT_CONFIG="/etc/xcontrol/account.yaml"
|
||||
# AND: `cp "${DEFAULT_CONFIG}" "${CONFIG_FILE}"` -- THIS LOGIC IS WEIRD if they are same path.
|
||||
# Ah, lines 4-5:
|
||||
# CONFIG_FILE="${CONFIG_PATH:-/etc/xcontrol/account.yaml}"
|
||||
# DEFAULT_CONFIG="/etc/xcontrol/account.yaml"
|
||||
# If CONFIG_PATH is set to something else, it copies from DEFAULT. But if DEFAULT is not there?
|
||||
# The Dockerfile DOES NOT copy config files to /etc/xcontrol.
|
||||
# Check Dockerfile again:
|
||||
# It copies entrypoint.sh. It does NOT copy config folder.
|
||||
# This means the original image probably expected config mounted or baked in separately?
|
||||
# Or maybe the builder stage had it? No, builder stage copies . . but runtime only copies /src/account binary.
|
||||
# The user might be mounting config at runtime.
|
||||
# However, for Cloud Run, we want to bake a default config or generate it.
|
||||
# Let's assume we need to provide a base config if one isn't there.
|
||||
# I will blindly respect the original logic but adding my specific injection logic.
|
||||
|
||||
# In my plan I said "Inject DB_PASSWORD".
|
||||
|
||||
if [ ! -f "${CONFIG_FILE}" ]; then
|
||||
# If we don't have a config file at all, we might be in trouble if we don't have a source.
|
||||
# Let's hope the user mounts it or we can generate a minimal one.
|
||||
# For now, let's assume the previous logic was correct for their env,
|
||||
# OR we can improve it by creating a default one if missing.
|
||||
if [ -f "/app/config/account.yaml" ]; then
|
||||
mkdir -p "$(dirname "${CONFIG_FILE}")"
|
||||
cp "/app/config/account.yaml" "${CONFIG_FILE}"
|
||||
elif [ -f "/usr/local/bin/account.yaml" ]; then
|
||||
mkdir -p "$(dirname "${CONFIG_FILE}")"
|
||||
cp "/usr/local/bin/account.yaml" "${CONFIG_FILE}"
|
||||
else
|
||||
echo "Warning: No configuration file found to copy from."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Modify Config if needed
|
||||
if [ -n "${PORT:-}" ]; then
|
||||
# Use sed for simpler replacement if format allows, but awk is robust
|
||||
tmp_cfg=$(mktemp)
|
||||
awk -v port="$PORT" '
|
||||
/^server:/ {print; in_server=1; addr_written=0; next}
|
||||
@ -23,7 +124,29 @@ if [ -n "${PORT:-}" ]; then
|
||||
}
|
||||
}
|
||||
' "${CONFIG_FILE}" > "${tmp_cfg}"
|
||||
CONFIG_FILE="${tmp_cfg}"
|
||||
mv "${tmp_cfg}" "${CONFIG_FILE}"
|
||||
fi
|
||||
|
||||
exec /usr/local/bin/accountsvc --config "${CONFIG_FILE}" "$@"
|
||||
# Inject DB Password into DSN if DB_PASSWORD is set
|
||||
if [ -n "${DB_PASSWORD:-}" ]; then
|
||||
# Simply replacing 'password' in the DSN if it matches the default pattern,
|
||||
# or appending if we want to be smarter.
|
||||
# Default DSN: postgres://shenlan:password@127.0.0.1:5432/account?sslmode=disable
|
||||
# We will try to replace ":password@" with ":${DB_PASSWORD}@"
|
||||
# This is a bit brittle but simple.
|
||||
sed -i "s|:password@|:${DB_PASSWORD}@|g" "${CONFIG_FILE}"
|
||||
fi
|
||||
|
||||
# Inject Auth Secrets
|
||||
if [ -n "${AUTH_PUBLIC_TOKEN:-}" ]; then
|
||||
sed -i "s|publicToken: \".*\"|publicToken: \"${AUTH_PUBLIC_TOKEN}\"|g" "${CONFIG_FILE}"
|
||||
fi
|
||||
if [ -n "${AUTH_REFRESH_SECRET:-}" ]; then
|
||||
sed -i "s|refreshSecret: \".*\"|refreshSecret: \"${AUTH_REFRESH_SECRET}\"|g" "${CONFIG_FILE}"
|
||||
fi
|
||||
if [ -n "${AUTH_ACCESS_SECRET:-}" ]; then
|
||||
sed -i "s|accessSecret: \".*\"|accessSecret: \"${AUTH_ACCESS_SECRET}\"|g" "${CONFIG_FILE}"
|
||||
fi
|
||||
|
||||
exec /usr/local/bin/account --config "${CONFIG_FILE}" "$@"
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user