diff --git a/.github/workflows/gpu-runner-overlay2-smoke.yml b/.github/workflows/gpu-runner-overlay2-smoke.yml new file mode 100644 index 00000000000..7b6131688ee --- /dev/null +++ b/.github/workflows/gpu-runner-overlay2-smoke.yml @@ -0,0 +1,121 @@ +name: GPU Runner overlay2 smoke test + +on: + workflow_dispatch: + inputs: + image: + description: Container image used for the pull and GPU test + required: true + default: ghcr.io/deepmodeling/abacus-cuda:latest + type: string + pull_request: + paths: + - '.github/workflows/gpu-runner-overlay2-smoke.yml' + +permissions: + contents: read + +concurrency: + group: gpu-runner-overlay2-smoke + cancel-in-progress: false + +jobs: + gpu-runner-smoke: + name: Validate overlay2, image pull, and GPU + runs-on: + - self-hosted + - gpu + - linux-x64 + timeout-minutes: 30 + env: + TEST_IMAGE: ${{ inputs.image }} + + steps: + - name: Verify Docker storage driver + shell: bash + run: | + set -euo pipefail + + driver="$(docker info --format '{{.Driver}}')" + docker_root="$(docker info --format '{{.DockerRootDir}}')" + driver_status="$(docker info --format '{{json .DriverStatus}}')" + + echo "Docker storage driver: ${driver}" + echo "Docker root: ${docker_root}" + echo "Driver status: ${driver_status}" + + { + echo "## GPU Runner smoke test" + echo + echo "- Runner: \`${RUNNER_NAME}\`" + echo "- Docker driver: \`${driver}\`" + echo "- Docker root: \`${docker_root}\`" + echo "- Test image: \`${TEST_IMAGE}\`" + } >> "${GITHUB_STEP_SUMMARY}" + + if [[ "${driver}" != "overlay2" ]]; then + echo "Expected Docker storage driver overlay2, got ${driver}." >&2 + exit 1 + fi + + - name: Pull test image and measure cold-pull time + id: cold_pull + shell: bash + run: | + set -euo pipefail + + started_at="$(date +%s)" + docker pull "${TEST_IMAGE}" + finished_at="$(date +%s)" + pull_seconds="$((finished_at - started_at))" + + image_size_bytes="$(docker image inspect "${TEST_IMAGE}" --format '{{.Size}}')" + image_size_gib="$(awk -v bytes="${image_size_bytes}" 'BEGIN { printf "%.2f", bytes / 1024 / 1024 / 1024 }')" + + echo "seconds=${pull_seconds}" >> "${GITHUB_OUTPUT}" + echo "size_gib=${image_size_gib}" >> "${GITHUB_OUTPUT}" + + { + echo "- Cold pull: \`${pull_seconds}s\`" + echo "- Image size: \`${image_size_gib} GiB\`" + } >> "${GITHUB_STEP_SUMMARY}" + + - name: Validate warm-cache pull + id: warm_pull + shell: bash + run: | + set -euo pipefail + + started_at="$(date +%s)" + docker pull "${TEST_IMAGE}" + finished_at="$(date +%s)" + pull_seconds="$((finished_at - started_at))" + + echo "seconds=${pull_seconds}" >> "${GITHUB_OUTPUT}" + echo "- Warm pull: \`${pull_seconds}s\`" >> "${GITHUB_STEP_SUMMARY}" + + - name: Run GPU smoke test inside the image + shell: bash + run: | + set -euo pipefail + + docker run --rm --gpus all "${TEST_IMAGE}" nvidia-smi + echo "- GPU container test: **passed**" >> "${GITHUB_STEP_SUMMARY}" + + - name: Report Docker disk usage + if: always() + shell: bash + run: | + docker system df || true + df -h /runner/_work || true + + { + echo + echo "### Timings" + echo + echo "| Check | Result |" + echo "|---|---:|" + echo "| Cold pull | ${{ steps.cold_pull.outputs.seconds || 'failed' }}s |" + echo "| Warm pull | ${{ steps.warm_pull.outputs.seconds || 'failed' }}s |" + echo "| Image size | ${{ steps.cold_pull.outputs.size_gib || 'unknown' }} GiB |" + } >> "${GITHUB_STEP_SUMMARY}" diff --git a/docs/community/cicd/overlay2-runner-sample.yaml b/docs/community/cicd/overlay2-runner-sample.yaml new file mode 100644 index 00000000000..b64d39e138d --- /dev/null +++ b/docs/community/cicd/overlay2-runner-sample.yaml @@ -0,0 +1,60 @@ +# CPU GitHub Actions Runner sample for ACK/ECI. +apiVersion: v1 +kind: ConfigMap +metadata: + name: docker-daemon-cpu-overlay2 + namespace: actions-runner-system +data: + daemon.json: | + { + "storage-driver": "overlay2", + "log-driver": "json-file", + "log-opts": { + "max-size": "10m", + "max-file": "3" + } + } +--- +apiVersion: actions.summerwind.dev/v1alpha1 +kind: RunnerDeployment +metadata: + name: cpu-runner + namespace: actions-runner-system +spec: + template: + metadata: + annotations: + k8s.aliyun.com/eci-auto-imc: "true" + k8s.aliyun.com/imc-perfect-match: "true" + k8s.aliyun.com/eci-extra-ephemeral-storage: "120Gi" + k8s.aliyun.com/eci-schedule-strategy: VSwitchOrdered + k8s.aliyun.com/eci-use-specs: ecs.c6.2xlarge + spec: + organization: deepmodeling + labels: + - cpu + - self-hosted + - linux-x64 + ephemeral: true + image: summerwind/actions-runner@sha256:ac52790f2737b2ef1be90a6708280cf2f0b2cf838f421ee7cce42d7511951976 + imagePullPolicy: IfNotPresent + dockerEnabled: true + dockerVolumeMounts: + - name: docker-config + mountPath: /etc/docker/daemon.json + subPath: daemon.json + - name: docker-storage + mountPath: /var/lib/docker + volumes: + - name: docker-config + configMap: + name: docker-daemon-cpu-overlay2 + - name: docker-storage + emptyDir: {} + nodeSelector: + type: virtual-kubelet + tolerations: + - key: virtual-kubelet.io/provider + operator: Equal + value: alibabacloud + effect: NoSchedule