Deploy billing-service from build artifact

This commit is contained in:
Haitao Pan 2026-04-12 19:06:49 +08:00
parent 8a18e547fc
commit 7fbc23257a
6 changed files with 165 additions and 16 deletions

View File

@ -26,6 +26,18 @@ jobs:
SERVICE_IMAGE_LATEST_REF: ghcr.io/${{ github.repository }}:latest
run: bash ./scripts/github-actions/build-service-image.sh
- name: Build linux binary artifact
env:
BILLING_SERVICE_BINARY_ARTIFACT: dist/billing-service-linux-amd64
run: bash ./scripts/github-actions/build-service-binary.sh
- name: Upload billing-service binary artifact
uses: actions/upload-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: billing-service-linux-amd64
path: dist/billing-service-linux-amd64
if-no-files-found: error
- name: Push image
run: bash ./scripts/github-actions/push-image-placeholder.sh
@ -35,12 +47,32 @@ jobs:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Download billing-service binary artifact
uses: actions/download-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: billing-service-linux-amd64
path: dist
- name: Install ansible
run: sudo apt-get update && sudo apt-get install -y ansible
- name: Configure deploy SSH
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
run: |
test -n "${SSH_PRIVATE_KEY}"
install -d -m 0700 ~/.ssh
printf '%s\n' "${SSH_PRIVATE_KEY}" > ~/.ssh/id_ed25519
chmod 0600 ~/.ssh/id_ed25519
ssh-keyscan -H jp-xhttp-contabo.svc.plus >> ~/.ssh/known_hosts
- name: Deploy via playbook
env:
IMAGE_REF: ${{ needs.build.outputs.service_image_ref }}
BILLING_SERVICE_IMAGE_REF: ${{ needs.build.outputs.service_image_ref }}
BILLING_SERVICE_IMAGE_TAG: ${{ needs.build.outputs.service_image_tag }}
BILLING_SERVICE_IMAGE_COMMIT: ${{ needs.build.outputs.service_image_commit }}
BILLING_SERVICE_BINARY_ARTIFACT: dist/billing-service-linux-amd64
DATABASE_URL: ${{ secrets.DATABASE_URL }}
INTERNAL_SERVICE_TOKEN: ${{ secrets.INTERNAL_SERVICE_TOKEN }}
STACK_TARGET_HOST: jp-xhttp-contabo.svc.plus
run: bash ./scripts/github-actions/deploy-billing-service.sh
validate:
@ -57,8 +89,18 @@ jobs:
- name: Verify traceability script cases
run: bash ./scripts/github-actions/test-validate-release-traceability.sh
- name: Configure validate SSH
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
run: |
test -n "${SSH_PRIVATE_KEY}"
install -d -m 0700 ~/.ssh
printf '%s\n' "${SSH_PRIVATE_KEY}" > ~/.ssh/id_ed25519
chmod 0600 ~/.ssh/id_ed25519
ssh-keyscan -H jp-xhttp-contabo.svc.plus >> ~/.ssh/known_hosts
- name: Validate runtime traceability
env:
SERVICE_IMAGE_REF: ${{ needs.build.outputs.service_image_ref }}
RUNTIME_PING_URL: https://accounts.svc.plus/api/ping
run: bash ./scripts/github-actions/validate-release-traceability.sh
STACK_TARGET_HOST: jp-xhttp-contabo.svc.plus
run: bash ./scripts/github-actions/validate-release-traceability-remote.sh

View File

@ -5,7 +5,9 @@ release identity.
## Runtime contract
- `IMAGE` must contain the full image reference used to start the container.
- `IMAGE` must contain the full release image reference produced by the build
job, even when the target host runs the service as a systemd binary instead
of a container.
- `/api/ping` returns `image`, `tag`, `commit`, and `version`.
- `tag`, `commit`, and `version` are derived from `IMAGE`.
- If `IMAGE` is missing or malformed, the derived fields stay empty instead of
@ -14,9 +16,13 @@ release identity.
## Pipeline contract
- Build must produce `service_image_ref` only from the full `GITHUB_SHA`.
- Deploy must consume `service_image_ref` and pass it through as the runtime
image identity.
- Validate must use `GET https://accounts.svc.plus/api/ping`.
- Build must also produce the linux billing-service binary artifact consumed by
deploy.
- Deploy must consume that build artifact directly and must not rebuild on the
target host.
- Deploy must pass `service_image_ref` through as the runtime image identity.
- Validate must query `billing-service` on the deployment target at
`http://127.0.0.1:8081/api/ping` over SSH.
- Validate must derive `tag` and `commit` from `service_image_ref` and compare
them against `/api/ping`.
- Validate must fail when runtime `image`, `tag`, `commit`, or `version` is
@ -27,11 +33,11 @@ release identity.
## External playbook alignment
The external `playbooks/deploy_billing_service.yml` playbook should accept
`IMAGE_REF` (or an equivalent full image reference variable), derive any
repo/tag helpers from it, and inject `IMAGE=<full image ref>` into the running
container environment for the service exposed through
`https://accounts.svc.plus/api/ping`.
`BILLING_SERVICE_BINARY_ARTIFACT` and `BILLING_SERVICE_IMAGE_REF`, deploy the
binary artifact to the target host without rebuilding it there, and inject
`IMAGE=<full image ref>` into `/etc/default/billing-service` for the runtime
served through `http://127.0.0.1:8081/api/ping`.
If `accounts.svc.plus/api/ping` keeps returning empty runtime metadata, treat
If `billing-service /api/ping` keeps returning empty runtime metadata, treat
that as a deployment contract failure: the runtime did not receive the full
`IMAGE` value and release traceability is broken.

View File

@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -euo pipefail
artifact_path="${BILLING_SERVICE_BINARY_ARTIFACT:?BILLING_SERVICE_BINARY_ARTIFACT is required}"
target_dir="$(dirname "${artifact_path}")"
mkdir -p "${target_dir}"
CGO_ENABLED="${CGO_ENABLED:-0}" \
GOOS="${GOOS:-linux}" \
GOARCH="${GOARCH:-amd64}" \
go build -buildvcs=false -o "${artifact_path}" ./cmd/billing-service
chmod 0755 "${artifact_path}"

View File

@ -1,5 +1,36 @@
#!/usr/bin/env bash
set -euo pipefail
test -n "${IMAGE_REF:?IMAGE_REF is required}"
ansible-playbook -i inventory playbooks/deploy_billing_service.yml
target_host="${STACK_TARGET_HOST:?STACK_TARGET_HOST is required}"
artifact_path="${BILLING_SERVICE_BINARY_ARTIFACT:?BILLING_SERVICE_BINARY_ARTIFACT is required}"
image_ref="${BILLING_SERVICE_IMAGE_REF:-${IMAGE_REF:-}}"
database_url="${DATABASE_URL:?DATABASE_URL is required}"
internal_service_token="${INTERNAL_SERVICE_TOKEN:?INTERNAL_SERVICE_TOKEN is required}"
playbooks_repo_url="${PLAYBOOKS_REPO_URL:-https://github.com/x-evor/playbooks.git}"
playbooks_repo_ref="${PLAYBOOKS_REPO_REF:-c0f1a1c2ee00e4131db2484c8cc00b2bc4dc1263}"
if [[ ! -f "${artifact_path}" ]]; then
echo "binary artifact not found: ${artifact_path}" >&2
exit 1
fi
if [[ -z "${image_ref}" ]]; then
echo "BILLING_SERVICE_IMAGE_REF or IMAGE_REF is required" >&2
exit 1
fi
workdir="$(mktemp -d)"
trap 'rm -rf "${workdir}"' EXIT
git clone --depth 1 "${playbooks_repo_url}" "${workdir}/playbooks"
git -C "${workdir}/playbooks" fetch --depth 1 origin "${playbooks_repo_ref}"
git -C "${workdir}/playbooks" checkout --detach FETCH_HEAD
export ANSIBLE_HOST_KEY_CHECKING=false
export BILLING_SERVICE_BINARY_ARTIFACT="$(cd "$(dirname "${artifact_path}")" && pwd)/$(basename "${artifact_path}")"
export BILLING_SERVICE_IMAGE_REF="${image_ref}"
export DATABASE_URL="${database_url}"
export INTERNAL_SERVICE_TOKEN="${internal_service_token}"
cd "${workdir}/playbooks"
ansible-playbook -i inventory.ini deploy_billing_service.yml --limit "${target_host}"

View File

@ -30,6 +30,34 @@ if not any(line.strip() == "- build" for line in validate_block):
if not any(line.strip() == "- deploy" for line in validate_block):
raise SystemExit("validate job must depend on deploy")
build_block = []
deploy_block = []
current_job = None
for line in lines:
if line.startswith(" build:"):
current_job = "build"
elif line.startswith(" deploy:"):
current_job = "deploy"
elif line.startswith(" validate:"):
current_job = "validate"
elif line.startswith(" ") and not line.startswith(" "):
current_job = None
if current_job == "build":
build_block.append(line)
elif current_job == "deploy":
deploy_block.append(line)
if not any("Upload billing-service binary artifact" in line for line in build_block):
raise SystemExit("build job must upload the billing-service binary artifact")
if not any("Download billing-service binary artifact" in line for line in deploy_block):
raise SystemExit("deploy job must download the billing-service binary artifact")
if not any("BILLING_SERVICE_IMAGE_REF: ${{ needs.build.outputs.service_image_ref }}" in line for line in deploy_block):
raise SystemExit("deploy job must consume needs.build.outputs.service_image_ref")
if not any("SERVICE_IMAGE_REF: ${{ needs.build.outputs.service_image_ref }}" in line for line in validate_block):
raise SystemExit("validate job must consume needs.build.outputs.service_image_ref")
PY

View File

@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail
service_image_ref="${SERVICE_IMAGE_REF:?SERVICE_IMAGE_REF is required}"
target_host="${STACK_TARGET_HOST:?STACK_TARGET_HOST is required}"
ssh_target="${RUNTIME_SSH_TARGET:-root@${target_host}}"
runtime_ping_path="${RUNTIME_PING_PATH:-http://127.0.0.1:8081/api/ping}"
tag="${service_image_ref##*:}"
commit="${tag#sha-}"
ssh -o BatchMode=yes "${ssh_target}" "systemctl is-active billing-service >/dev/null"
runtime_payload="$(ssh -o BatchMode=yes "${ssh_target}" "curl -fsS ${runtime_ping_path}")"
jq -e \
--arg image "${service_image_ref}" \
--arg tag "${tag}" \
--arg commit "${commit}" \
'
(.image | type == "string" and length > 0) and
(.tag | type == "string" and length > 0) and
(.commit | type == "string" and length > 0) and
(.version | type == "string" and length > 0) and
.image == $image and
.tag == $tag and
.commit == $commit and
.version == $commit
' <<<"${runtime_payload}" >/dev/null