Skip to content

Commit 14e087e

Browse files
committed
Set up CI/CD pipeline for CI containers
1 parent 3d18e54 commit 14e087e

21 files changed

+1006
-0
lines changed

.github/runs-on.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Custom images with CUDA toolkit installed
2+
# See vm_images/ for instructions for building the images
3+
images:
4+
linux-amd64:
5+
platform: "linux"
6+
arch: "x64"
7+
owner: "492475357299" # XGBooost CI
8+
name: "xgboost-ci-runs-on-linux-*"
9+
windows-amd64:
10+
platform: "windows"
11+
arch: "x64"
12+
owner: "492475357299" # XGBooost CI
13+
name: "xgboost-ci-runs-on-windows-*"
14+
15+
runners:
16+
linux-amd64-cpu:
17+
cpu: 16
18+
family: ["c7i-flex", "c7i", "c7a", "c5", "c5a"]
19+
image: linux-amd64
20+
linux-amd64-gpu:
21+
family: ["g4dn.xlarge"]
22+
image: linux-amd64
23+
linux-amd64-mgpu:
24+
family: ["g4dn.12xlarge"]
25+
image: linux-amd64
26+
linux-arm64-cpu:
27+
cpu: 16
28+
family: ["c6g", "c7g"]
29+
image: ubuntu24-full-arm64

.github/workflows/containers.yml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
name: Build and publish image
2+
run-name: "Build image${{ (inputs.upstream_repository != '') && format(' - triggered by: {0}', inputs.upstream_repository) || '' }}"
3+
4+
on:
5+
workflow_dispatch:
6+
inputs:
7+
upstream_repository:
8+
required: false
9+
type: string
10+
upstream_job:
11+
required: false
12+
type: string
13+
push:
14+
branches:
15+
- main
16+
pull_request:
17+
schedule:
18+
- cron: "0 7 * * *" # Run once daily
19+
20+
concurrency:
21+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
22+
cancel-in-progress: true
23+
24+
env:
25+
BRANCH_NAME: >-
26+
${{ github.event.pull_request.number && 'PR-' }}${{ github.event.pull_request.number || github.ref_name }}
27+
PUBLISH_CONTAINER: 1
28+
29+
jobs:
30+
build-containers:
31+
name: Build CI containers (${{ matrix.container_id }})
32+
runs-on:
33+
- runs-on
34+
- runner=${{ matrix.runner }}
35+
- run-id=${{ github.run_id }}
36+
- tag=build-containers-${{ matrix.container_id }}
37+
strategy:
38+
matrix:
39+
container_id:
40+
- xgb-ci.clang_tidy
41+
- xgb-ci.cpu
42+
- xgb-ci.gpu
43+
- xgb-ci.gpu_build_r_rockylinux8
44+
- xgb-ci.gpu_build_rockylinux8
45+
- xgb-ci.gpu_build_rockylinux8_dev_ver
46+
- xgb-ci.jvm
47+
- xgb-ci.jvm_gpu_build
48+
- xgb-ci.manylinux_2_28_x86_64
49+
- xgb-ci.manylinux2014_x86_64
50+
runner: [linux-amd64-cpu]
51+
include:
52+
- container_id: xgb-ci.aarch64
53+
runner: linux-arm64-cpu
54+
- container_id: xgb-ci.manylinux2014_aarch64
55+
runner: linux-arm64-cpu
56+
steps:
57+
# Restart Docker daemon so that it recognizes the ephemeral disks
58+
- run: sudo systemctl restart docker
59+
- uses: actions/checkout@v4
60+
with:
61+
submodules: "true"
62+
- name: Build ${{ matrix.container_id }}
63+
run: bash container/docker_build.sh ${{ matrix.container_id }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__pycache__

containers/ci_container.yml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
## List of CI containers with definitions and build arguments
2+
3+
# Each container will be built using the definition from
4+
# containers/dockerfile/Dockerfile.CONTAINER_DEF
5+
6+
rapids_versions:
7+
stable: &rapids_version "24.10"
8+
dev: &dev_rapids_version "24.12"
9+
10+
xgb-ci.gpu_build_rockylinux8:
11+
container_def: gpu_build_rockylinux8
12+
build_args:
13+
CUDA_VERSION: "12.4.1"
14+
NCCL_VERSION: "2.23.4-1"
15+
RAPIDS_VERSION: *rapids_version
16+
17+
xgb-ci.gpu_build_rockylinux8_dev_ver:
18+
container_def: gpu_build_rockylinux8
19+
build_args:
20+
CUDA_VERSION: "12.4.1"
21+
NCCL_VERSION: "2.23.4-1"
22+
RAPIDS_VERSION: *dev_rapids_version
23+
24+
xgb-ci.gpu_build_r_rockylinux8:
25+
container_def: gpu_build_r_rockylinux8
26+
build_args:
27+
CUDA_VERSION: "12.4.1"
28+
R_VERSION: "4.3.2"
29+
30+
xgb-ci.gpu:
31+
container_def: gpu
32+
build_args:
33+
CUDA_VERSION: "12.4.1"
34+
NCCL_VERSION: "2.23.4-1"
35+
RAPIDS_VERSION: *rapids_version
36+
37+
xgb-ci.gpu_dev_ver:
38+
container_def: gpu
39+
build_args:
40+
CUDA_VERSION: "12.4.1"
41+
NCCL_VERSION: "2.23.4-1"
42+
RAPIDS_VERSION: *dev_rapids_version
43+
RAPIDSAI_CONDA_CHANNEL: "rapidsai-nightly"
44+
45+
xgb-ci.clang_tidy:
46+
container_def: clang_tidy
47+
build_args:
48+
CUDA_VERSION: "12.4.1"
49+
50+
xgb-ci.cpu:
51+
container_def: cpu
52+
53+
xgb-ci.aarch64:
54+
container_def: aarch64
55+
56+
xgb-ci.manylinux_2_28_x86_64:
57+
container_def: manylinux_2_28_x86_64
58+
59+
xgb-ci.manylinux2014_x86_64:
60+
container_def: manylinux2014_x86_64
61+
62+
xgb-ci.manylinux2014_aarch64:
63+
container_def: manylinux2014_aarch64
64+
65+
xgb-ci.jvm:
66+
container_def: jvm
67+
68+
xgb-ci.jvm_gpu_build:
69+
container_def: jvm_gpu_build
70+
build_args:
71+
CUDA_VERSION: "12.4.1"
72+
NCCL_VERSION: "2.23.4-1"

containers/docker_build.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
"""
2+
Wrapper script to build a Docker container
3+
"""
4+
5+
import argparse
6+
import itertools
7+
import pathlib
8+
import subprocess
9+
import sys
10+
import textwrap
11+
12+
PROJECT_ROOT_DIR = pathlib.Path(__file__).parent.parent
13+
LINEWIDTH = 88
14+
TEXT_WRAPPER = textwrap.TextWrapper(
15+
width=LINEWIDTH,
16+
initial_indent="",
17+
subsequent_indent=" ",
18+
break_long_words=False,
19+
break_on_hyphens=False,
20+
)
21+
22+
23+
def fancy_print_cli_args(cli_args: list[str]) -> None:
24+
print(
25+
"=" * LINEWIDTH
26+
+ "\n"
27+
+ " \\\n".join(TEXT_WRAPPER.wrap(" ".join(cli_args)))
28+
+ "\n"
29+
+ "=" * LINEWIDTH
30+
+ "\n",
31+
flush=True,
32+
)
33+
34+
35+
def parse_build_args(*, raw_build_args: list[str]) -> dict[str, str]:
36+
parsed_build_args = dict()
37+
for arg in raw_build_args:
38+
try:
39+
key, value = arg.split("=", maxsplit=1)
40+
except ValueError as e:
41+
raise ValueError(
42+
f"Build argument must be of form KEY=VALUE. Got: {arg}"
43+
) from e
44+
parsed_build_args[key] = value
45+
return parsed_build_args
46+
47+
48+
def docker_build(
49+
*,
50+
container_tag: str,
51+
build_args: dict[str, str],
52+
dockerfile_path: pathlib.Path,
53+
docker_context_path: pathlib.Path,
54+
) -> None:
55+
## Set up command-line arguments to be passed to `docker build`
56+
# Build args
57+
docker_build_cli_args = list(
58+
itertools.chain.from_iterable(
59+
[["--build-arg", f"{k}={v}"] for k, v in build_args.items()]
60+
)
61+
)
62+
# When building an image using a non-default driver, we need to specify
63+
# `--load` to load it to the image store.
64+
# See https://docs.docker.com/build/builders/drivers/
65+
docker_build_cli_args.append("--load")
66+
# Remaining CLI args
67+
docker_build_cli_args.extend(
68+
[
69+
"--progress=plain",
70+
"--ulimit",
71+
"nofile=1024000:1024000",
72+
"-t",
73+
container_tag,
74+
"-f",
75+
str(dockerfile_path),
76+
str(docker_context_path),
77+
]
78+
)
79+
cli_args = ["docker", "build"] + docker_build_cli_args
80+
fancy_print_cli_args(cli_args)
81+
subprocess.run(cli_args, check=True, encoding="utf-8")
82+
83+
84+
def main(*, args: argparse.Namespace) -> None:
85+
docker_context_path = PROJECT_ROOT_DIR / "containers"
86+
dockerfile_path = (
87+
docker_context_path / "dockerfile" / f"Dockerfile.{args.container_def}"
88+
)
89+
90+
build_args = parse_build_args(raw_build_args=args.build_arg)
91+
92+
docker_build(
93+
container_tag=args.container_tag,
94+
build_args=build_args,
95+
dockerfile_path=dockerfile_path,
96+
docker_context_path=docker_context_path,
97+
)
98+
99+
100+
if __name__ == "__main__":
101+
parser = argparse.ArgumentParser(description="Build a Docker container")
102+
parser.add_argument(
103+
"--container-def",
104+
type=str,
105+
required=True,
106+
help=(
107+
"String uniquely identifying the container definition. The container "
108+
"definition will be fetched from "
109+
"containers/dockerfile/Dockerfile.CONTAINER_DEF."
110+
),
111+
)
112+
parser.add_argument(
113+
"--container-tag",
114+
type=str,
115+
required=True,
116+
help=(
117+
"Tag to assign to the newly built container, e.g. "
118+
"492475357299.dkr.ecr.us-west-2.amazonaws.com/xgb-ci.gpu:master"
119+
),
120+
)
121+
parser.add_argument(
122+
"--build-arg",
123+
type=str,
124+
default=[],
125+
action="append",
126+
help=(
127+
"Build-time variable(s) to be passed to `docker build`. Each variable "
128+
"should be specified as a key-value pair in the form KEY=VALUE. "
129+
"The variables should match the ARG instructions in the Dockerfile. "
130+
"When passing multiple variables, specify --build-arg multiple times. "
131+
"Example: --build-arg CUDA_VERSION_ARG=12.5 --build-arg RAPIDS_VERSION_ARG=24.10"
132+
),
133+
)
134+
135+
if len(sys.argv) == 1:
136+
parser.print_help()
137+
sys.exit(1)
138+
139+
parsed_args = parser.parse_args()
140+
main(args=parsed_args)

0 commit comments

Comments
 (0)