작업이 사용하는 컨테이너 커스터마이징하기
작업이 사용하는 컨테이너 커스터마이징하기
자체 호스팅 러너가 작업을 위해 컨테이너를 호출하는 방식을 커스터마이징할 수 있어요. Kubernetes나 Podman을 통해 컨테이너를 관리하거나, 컨테이너를 호출하는 데 사용되는 docker run 또는 docker create 명령을 커스터마이징할 수 있어요.
출처: 문서
본문
Note
이 기능은 현재 공개 미리보기(public preview) 상태이며 변경될 수 있어요.
컨테이너 커스터마이징 정보
GitHub Actions는 워크플로 파일의 container: 문을 사용해 컨테이너 내에서 작업을 실행할 수 있게 해줘요. 자세한 내용은 컨테이너에서 작업 실행하기를 참고하세요. 컨테이너 기반 작업을 처리하려면 자체 호스팅 러너가 각 작업에 대한 컨테이너를 만들어요.
GitHub Actions는 자체 호스팅 러너가 컨테이너를 만들 방식을 커스터마이징할 수 있는 명령을 지원해요. 예를 들어 이 명령들을 사용해 Kubernetes나 Podman을 통해 컨테이너를 관리하고, 컨테이너를 호출하는 데 사용되는 docker run 또는 docker create 명령도 커스터마이징할 수 있어요. 커스터마이징 명령은 러너에 특정 환경 변수가 설정되면 자동으로 트리거되는 스크립트에 의해 실행돼요. 자세한 내용은 아래 커스터마이징 스크립트 트리거하기를 참고하세요.
이 커스터마이징은 Linux 기반 자체 호스팅 러너에서만 사용할 수 있으며, root 사용자 접근은 필요하지 않아요.
컨테이너 커스터마이징 명령
GitHub Actions는 컨테이너 커스터마이징을 위한 다음 명령을 포함해요:
prepare_job: 작업이 시작될 때 호출돼요.cleanup_job: 작업이 끝날 때 호출돼요.run_container_step: 작업의 각 컨테이너 액션에 대해 한 번씩 호출돼요.run_script_step: 컨테이너 액션이 아닌 모든 단계를 실행해요.
이 커스터마이징 명령 각각은 자체 JSON 파일에 정의되어야 해요. 파일 이름은 확장자 .json을 붙여 명령 이름과 일치해야 해요. 예를 들어 prepare_job 명령은 prepare_job.json에 정의돼요. 이 JSON 파일들은 기본 index.js 스크립트의 일부로 자체 호스팅 러너에서 함께 실행돼요. 이 과정은 커스터마이징 스크립트 생성하기에서 더 자세히 설명돼요.
이 명령들은 아래에서 더 자세히 설명되는 구성 인자도 포함해요.
prepare_job
prepare_job 명령은 작업이 시작될 때 호출돼요. GitHub Actions는 작업의 모든 작업 또는 서비스 컨테이너를 전달해요. 작업에 서비스 또는 작업 컨테이너가 있으면 이 명령이 호출돼요.
GitHub Actions는 prepare_job 명령에서 다음 작업을 수행할 것으로 가정해요:
- 필요한 경우 이전 작업의 것을 정리(prune)해요.
- 필요한 경우 네트워크를 만들어요.
- 작업 및 서비스 컨테이너를 풀해요.
- 작업 컨테이너를 시작해요.
- 서비스 컨테이너를 시작해요.
- GitHub Actions가 필요로 하는 정보를 응답 파일에 기록해요:
- 필수: 컨테이너가
alpineLinux 컨테이너인지 여부를 (isAlpineboolean으로) 명시해요. - 선택: 작업 컨텍스트에 설정하려는 모든 컨텍스트 필드. 그렇지 않으면 사용자가 사용할 수 없게 돼요. 자세한 내용은 컨텍스트 참조를 참고하세요.
- 필수: 컨테이너가
- 헬스 체크가 성공하고 작업/서비스 컨테이너가 시작되면
0을 반환해요.
prepare_job의 인자
jobContainer: 선택. 지정된 작업 컨테이너에 대한 정보를 담은 객체.image: 필수. Docker 이미지를 담은 문자열.workingDirectory: 필수. 작업 디렉토리의 절대 경로를 담은 문자열.createOptions: 선택. YAML에 지정된 선택적 create 옵션. 자세한 내용은 컨테이너에서 작업 실행하기를 참고하세요.environmentVariables: 선택. 키 환경 변수의 맵을 설정해요.userMountVolumes: 선택. YAML에 설정된 사용자 마운트 볼륨의 배열. 자세한 내용은 컨테이너에서 작업 실행하기를 참고하세요.sourceVolumePath: 필수. Docker 컨테이너에 마운트될 볼륨의 소스 경로.targetVolumePath: 필수. Docker 컨테이너에 마운트될 볼륨의 대상 경로.readOnly: 필수. 마운트가 읽기 전용이어야 하는지 여부를 결정해요.
systemMountVolumes: 필수. 컨테이너에 마운트할 마운트 배열, 위와 동일한 필드.sourceVolumePath: 필수. Docker 컨테이너에 마운트될 볼륨의 소스 경로.targetVolumePath: 필수. Docker 컨테이너에 마운트될 볼륨의 대상 경로.readOnly: 필수. 마운트가 읽기 전용이어야 하는지 여부를 결정해요.
registry선택. 비공개 컨테이너 레지스트리의 Docker 레지스트리 자격 증명.username: 선택. 레지스트리 계정의 사용자 이름.password: 선택. 레지스트리 계정의 비밀번호.serverUrl: 선택. 레지스트리 URL.
portMappings: 선택. 컨테이너에 매핑할 source:target 포트의 키-값 해시.
services: 선택. 시작할 서비스 컨테이너의 배열.contextName: 필수. 작업 컨텍스트에서 서비스의 이름.image: 필수. Docker 이미지를 담은 문자열.createOptions: 선택. YAML에 지정된 선택적 create 옵션. 자세한 내용은 컨테이너에서 작업 실행하기를 참고하세요.environmentVariables: 선택. 키 환경 변수의 맵을 설정해요.userMountVolumes: 선택. 컨테이너에 마운트할 마운트 배열, 위와 동일한 필드.sourceVolumePath: 필수. Docker 컨테이너에 마운트될 볼륨의 소스 경로.targetVolumePath: 필수. Docker 컨테이너에 마운트될 볼륨의 대상 경로.readOnly: 필수. 마운트가 읽기 전용이어야 하는지 여부를 결정해요.
registry선택. 비공개 컨테이너 레지스트리의 Docker 레지스트리 자격 증명.username: 선택. 레지스트리 계정의 사용자 이름.password: 선택. 레지스트리 계정의 비밀번호.serverUrl: 선택. 레지스트리 URL.
portMappings: 선택. 컨테이너에 매핑할 source:target 포트의 키-값 해시.
prepare_job의 예제 입력
{
"command": "prepare_job",
"responseFile": "/users/octocat/runner/_work/{guid}.json",
"state": {},
"args": {
"jobContainer": {
"image": "node:18",
"workingDirectory": "/__w/octocat-test2/octocat-test2",
"createOptions": "--cpus 1",
"environmentVariables": {
"NODE_ENV": "development"
},
"userMountVolumes": [
{
"sourceVolumePath": "my_docker_volume",
"targetVolumePath": "/volume_mount",
"readOnly": false
}
],
"systemMountVolumes": [
{
"sourceVolumePath": "/home/octocat/git/runner/_layout/_work",
"targetVolumePath": "/__w",
"readOnly": false
},
{
"sourceVolumePath": "/home/octocat/git/runner/_layout/externals",
"targetVolumePath": "/__e",
"readOnly": true
},
{
"sourceVolumePath": "/home/octocat/git/runner/_layout/_work/_temp",
"targetVolumePath": "/__w/_temp",
"readOnly": false
},
{
"sourceVolumePath": "/home/octocat/git/runner/_layout/_work/_actions",
"targetVolumePath": "/__w/_actions",
"readOnly": false
},
{
"sourceVolumePath": "/home/octocat/git/runner/_layout/_work/_tool",
"targetVolumePath": "/__w/_tool",
"readOnly": false
},
{
"sourceVolumePath": "/home/octocat/git/runner/_layout/_work/_temp/_github_home",
"targetVolumePath": "/github/home",
"readOnly": false
},
{
"sourceVolumePath": "/home/octocat/git/runner/_layout/_work/_temp/_github_workflow",
"targetVolumePath": "/github/workflow",
"readOnly": false
}
],
"registry": {
"username": "octocat",
"password": "examplePassword",
"serverUrl": "https://index.docker.io/v1"
},
"portMappings": { "80": "801" }
},
"services": [
{
"contextName": "redis",
"image": "redis",
"createOptions": "--cpus 1",
"environmentVariables": {},
"userMountVolumes": [],
"portMappings": { "80": "801" },
"registry": {
"username": "octocat",
"password": "examplePassword",
"serverUrl": "https://index.docker.io/v1"
}
}
]
}
}
prepare_job의 예제 출력
이 예제 출력은 위 입력에 정의된 responseFile의 내용이에요.
{
"state": {
"network": "example_network_53269bd575972817b43f7733536b200c",
"jobContainer": "82e8219701fe096a35941d869cf3d71af1d943b5d8bdd718857fb87ac3042480",
"serviceContainers": {
"redis": "60972d9aa486605e66b0dad4abb678dc3d9116f536579e418176eedb8abb9105"
}
},
"context": {
"container": {
"id": "82e8219701fe096a35941d869cf3d71af1d943b5d8bdd718857fb87ac3042480",
"network": "example_network_53269bd575972817b43f7733536b200c"
},
"services": {
"redis": {
"id": "60972d9aa486605e66b0dad4abb678dc3d9116f536579e418176eedb8abb9105",
"ports": {
"8080": "8080"
},
"network": "example_network_53269bd575972817b43f7733536b200c"
}
},
"isAlpine": true
}
}
cleanup_job
cleanup_job 명령은 작업이 끝날 때 호출돼요. GitHub Actions는 cleanup_job 명령에서 다음 작업을 수행할 것으로 가정해요:
- 실행 중인 서비스 또는 작업 컨테이너(또는 이에 상응하는 pod)를 중지해요.
- 네트워크를 중지해요(존재하는 경우).
- 작업 또는 서비스 컨테이너(또는 이에 상응하는 pod)를 삭제해요.
- 네트워크를 삭제해요(존재하는 경우).
- 작업을 위해 만들어진 다른 모든 것을 정리해요.
cleanup_job의 인자
cleanup_job에는 인자가 제공되지 않아요.
cleanup_job의 예제 입력
{
"command": "cleanup_job",
"responseFile": null,
"state": {
"network": "example_network_53269bd575972817b43f7733536b200c",
"jobContainer": "82e8219701fe096a35941d869cf3d71af1d943b5d8bdd718857fb87ac3042480",
"serviceContainers": {
"redis": "60972d9aa486605e66b0dad4abb678dc3d9116f536579e418176eedb8abb9105"
}
},
"args": {}
}
cleanup_job의 예제 출력
cleanup_job에는 출력이 예상되지 않아요.
run_container_step
run_container_step 명령은 작업의 각 컨테이너 액션에 대해 한 번씩 호출돼요. GitHub Actions는 run_container_step 명령에서 다음 작업을 수행할 것으로 가정해요:
- 필요한 컨테이너를 풀하거나 빌드해요(할 수 없으면 실패해요).
- 컨테이너 액션을 실행하고 컨테이너의 종료 코드를 반환해요.
- 단계 로그 출력을 stdout과 stderr로 스트리밍해요.
- 컨테이너가 실행된 후 정리해요.
run_container_step의 인자
image: 선택. Docker 이미지를 담은 문자열. 그렇지 않으면 Dockerfile이 제공되어야 해요.dockerfile: 선택. Dockerfile 경로를 담은 문자열. 그렇지 않으면 이미지가 제공되어야 해요.entryPointArgs: 선택. 엔트리 포인트 인자를 담은 목록.entryPoint: 선택. 기본 이미지 엔트리포인트를 덮어써야 할 때 사용할 컨테이너 엔트리 포인트.workingDirectory: 필수. 작업 디렉토리의 절대 경로를 담은 문자열.createOptions: 선택. YAML에 지정된 선택적 create 옵션. 자세한 내용은 컨테이너에서 작업 실행하기를 참고하세요.environmentVariables: 선택. 키 환경 변수의 맵을 설정해요.prependPath: 선택.$PATH변수 앞에 추가할 추가 경로의 배열.userMountVolumes: 선택. YAML에 설정된 사용자 마운트 볼륨의 배열. 자세한 내용은 컨테이너에서 작업 실행하기를 참고하세요.sourceVolumePath: 필수. Docker 컨테이너에 마운트될 볼륨의 소스 경로.targetVolumePath: 필수. Docker 컨테이너에 마운트될 볼륨의 대상 경로.readOnly: 필수. 마운트가 읽기 전용이어야 하는지 여부를 결정해요.
systemMountVolumes: 필수. 위와 동일한 필드를 사용해 컨테이너에 마운트할 마운트 배열.sourceVolumePath: 필수. Docker 컨테이너에 마운트될 볼륨의 소스 경로.targetVolumePath: 필수. Docker 컨테이너에 마운트될 볼륨의 대상 경로.readOnly: 필수. 마운트가 읽기 전용이어야 하는지 여부를 결정해요.
registry선택. 비공개 컨테이너 레지스트리의 Docker 레지스트리 자격 증명.username: 선택. 레지스트리 계정의 사용자 이름.password: 선택. 레지스트리 계정의 비밀번호.serverUrl: 선택. 레지스트리 URL.
portMappings: 선택. 컨테이너에 매핑할 source:target 포트의 키-값 해시.
이미지에 대한 예제 입력
Docker 이미지를 사용한다면 "image": 파라미터에 이미지 이름을 지정할 수 있어요.
{
"command": "run_container_step",
"responseFile": null,
"state": {
"network": "example_network_53269bd575972817b43f7733536b200c",
"jobContainer": "82e8219701fe096a35941d869cf3d71af1d943b5d8bdd718857fb87ac3042480",
"serviceContainers": {
"redis": "60972d9aa486605e66b0dad4abb678dc3d9116f536579e418176eedb8abb9105"
}
},
"args": {
"image": "node:18",
"dockerfile": null,
"entryPointArgs": ["-f", "/dev/null"],
"entryPoint": "tail",
"workingDirectory": "/__w/octocat-test2/octocat-test2",
"createOptions": "--cpus 1",
"environmentVariables": {
"NODE_ENV": "development"
},
"prependPath": ["/foo/bar", "bar/foo"],
"userMountVolumes": [
{
"sourceVolumePath": "my_docker_volume",
"targetVolumePath": "/volume_mount",
"readOnly": false
}
],
"systemMountVolumes": [
{
"sourceVolumePath": "/home/octocat/git/runner/_layout/_work",
"targetVolumePath": "/__w",
"readOnly": false
},
{
"sourceVolumePath": "/home/octocat/git/runner/_layout/externals",
"targetVolumePath": "/__e",
"readOnly": true
},
{
"sourceVolumePath": "/home/octocat/git/runner/_layout/_work/_temp",
"targetVolumePath": "/__w/_temp",
"readOnly": false
},
{
"sourceVolumePath": "/home/octocat/git/runner/_layout/_work/_actions",
"targetVolumePath": "/__w/_actions",
"readOnly": false
},
{
"sourceVolumePath": "/home/octocat/git/runner/_layout/_work/_tool",
"targetVolumePath": "/__w/_tool",
"readOnly": false
},
{
"sourceVolumePath": "/home/octocat/git/runner/_layout/_work/_temp/_github_home",
"targetVolumePath": "/github/home",
"readOnly": false
},
{
"sourceVolumePath": "/home/octocat/git/runner/_layout/_work/_temp/_github_workflow",
"targetVolumePath": "/github/workflow",
"readOnly": false
}
],
"registry": null,
"portMappings": { "80": "801" }
}
}
Dockerfile에 대한 예제 입력
컨테이너가 Dockerfile로 정의된 경우, 이 예제는 "dockerfile": 파라미터를 사용해 입력에서 Dockerfile 경로를 지정하는 방법을 보여줘요.
{
"command": "run_container_step",
"responseFile": null,
"state": {
"network": "example_network_53269bd575972817b43f7733536b200c",
"jobContainer": "82e8219701fe096a35941d869cf3d71af1d943b5d8bdd718857fb87ac3042480",
"services": {
"redis": "60972d9aa486605e66b0dad4abb678dc3d9116f536579e418176eedb8abb9105"
}
},
"args": {
"image": null,
"dockerfile": "/__w/_actions/foo/dockerfile",
"entryPointArgs": ["hello world"],
"entryPoint": "echo",
"workingDirectory": "/__w/octocat-test2/octocat-test2",
"createOptions": "--cpus 1",
"environmentVariables": {
"NODE_ENV": "development"
},
"prependPath": ["/foo/bar", "bar/foo"],
"userMountVolumes": [
{
"sourceVolumePath": "my_docker_volume",
"targetVolumePath": "/volume_mount",
"readOnly": false
}
],
"systemMountVolumes": [
{
"sourceVolumePath": "/home/octocat/git/runner/_layout/_work",
"targetVolumePath": "/__w",
"readOnly": false
},
{
"sourceVolumePath": "/home/octocat/git/runner/_layout/externals",
"targetVolumePath": "/__e",
"readOnly": true
},
{
"sourceVolumePath": "/home/octocat/git/runner/_layout/_work/_temp",
"targetVolumePath": "/__w/_temp",
"readOnly": false
},
{
"sourceVolumePath": "/home/octocat/git/runner/_layout/_work/_actions",
"targetVolumePath": "/__w/_actions",
"readOnly": false
},
{
"sourceVolumePath": "/home/octocat/git/runner/_layout/_work/_tool",
"targetVolumePath": "/__w/_tool",
"readOnly": false
},
{
"sourceVolumePath": "/home/octocat/git/runner/_layout/_work/_temp/_github_home",
"targetVolumePath": "/github/home",
"readOnly": false
},
{
"sourceVolumePath": "/home/octocat/git/runner/_layout/_work/_temp/_github_workflow",
"targetVolumePath": "/github/workflow",
"readOnly": false
}
],
"registry": null,
"portMappings": { "80": "801" }
}
}
run_container_step의 예제 출력
run_container_step에는 출력이 예상되지 않아요.
run_script_step
GitHub Actions는 다음 작업을 수행할 것으로 가정해요:
- 제공된 스크립트를 작업 컨테이너 내에서 호출하고 종료 코드를 반환해요.
- 단계 로그 출력을 stdout과 stderr로 스트리밍해요.
run_script_step의 인자
entryPointArgs: 선택. 엔트리 포인트 인자를 담은 목록.entryPoint: 선택. 기본 이미지 엔트리포인트를 덮어써야 할 때 사용할 컨테이너 엔트리 포인트.prependPath: 선택.$PATH변수 앞에 추가할 추가 경로의 배열.workingDirectory: 필수. 작업 디렉토리의 절대 경로를 담은 문자열.environmentVariables: 선택. 키 환경 변수의 맵을 설정해요.
run_script_step의 예제 입력
{
"command": "run_script_step",
"responseFile": null,
"state": {
"network": "example_network_53269bd575972817b43f7733536b200c",
"jobContainer": "82e8219701fe096a35941d869cf3d71af1d943b5d8bdd718857fb87ac3042480",
"serviceContainers": {
"redis": "60972d9aa486605e66b0dad4abb678dc3d9116f536579e418176eedb8abb9105"
}
},
"args": {
"entryPointArgs": ["-e", "/runner/temp/example.sh"],
"entryPoint": "bash",
"environmentVariables": {
"NODE_ENV": "development"
},
"prependPath": ["/foo/bar", "bar/foo"],
"workingDirectory": "/__w/octocat-test2/octocat-test2"
}
}
run_script_step의 예제 출력
run_script_step에는 출력이 예상되지 않아요.
커스터마이징 스크립트 생성하기
GitHub은 Docker와 Kubernetes용 커스터마이징 스크립트를 생성하는 방법을 보여주는 예제 저장소를 만들었어요.
Note
결과 스크립트는 테스트 목적으로 제공되며, 요구 사항에 적합한지 직접 판단해야 해요.
-
actions/runner-container-hooks 저장소를 자체 호스팅 러너에 클론해요.
-
examples/디렉토리에는 각각 자체 JSON 파일이 있는 기존 커스터마이징 명령이 포함되어 있어요. 이 예제들을 검토하고 나만의 커스터마이징 명령의 시작점으로 사용할 수 있어요.prepare_job.jsonrun_script_step.jsonrun_container_step.json
-
npm 패키지를 빌드해요. 이 명령들은
packages/docker/dist와packages/k8s/dist안에index.js파일을 생성해요.npm install && npm run bootstrap && npm run build-all
결과 index.js가 GitHub Actions에 의해 트리거되면 JSON 파일에 정의된 커스터마이징 명령을 실행해요. index.js를 트리거하려면 다음 섹션에서 설명하는 대로 ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER 환경 변수에 추가해야 해요.
커스터마이징 스크립트 트리거하기
커스텀 스크립트는 러너에 있어야 하지만, 자체 호스팅 러너 애플리케이션 디렉토리(러너 소프트웨어를 다운로드해 압축을 푼 디렉토리)에 저장해서는 안 돼요. 스크립트는 러너 서비스를 실행하는 서비스 계정의 보안 컨텍스트에서 실행돼요.
Note
트리거된 스크립트는 동기적으로 처리되므로, 실행되는 동안 작업 실행을 차단해요.
러너가 스크립트의 절대 경로를 담은 다음 환경 변수를 가질 때 스크립트가 자동으로 실행돼요:
ACTIONS_RUNNER_CONTAINER_HOOKS: 이 환경 변수에 정의된 스크립트는 작업이 러너에 할당되었지만 작업이 실행되기 전에 트리거돼요.
이 환경 변수를 설정하려면 운영 체제에 추가하거나, 자체 호스팅 러너 애플리케이션 디렉토리 내의 .env라는 파일에 추가할 수 있어요. 예를 들어 다음 .env 항목은 각 컨테이너 기반 작업이 실행되기 전에 러너가 /Users/octocat/runner/index.js 스크립트를 자동으로 실행하게 해요:
ACTIONS_RUNNER_CONTAINER_HOOKS=/Users/octocat/runner/index.js
작업이 항상 컨테이너 내에서 실행되도록 하고, 결과적으로 항상 컨테이너 커스터마이징을 적용하려면 자체 호스팅 러너에 ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER 변수를 true로 설정할 수 있어요. 이렇게 하면 작업 컨테이너를 지정하지 않는 작업은 실패해요.
문제 해결
타임아웃 설정 없음
현재 ACTIONS_RUNNER_CONTAINER_HOOKS가 실행하는 스크립트에 대해 사용 가능한 타임아웃 설정이 없어요. 결과적으로 스크립트에 타임아웃 처리를 추가하는 것을 고려할 수 있어요.
워크플로 실행 로그 검토하기
스크립트가 실행되고 있는지 확인하려면 해당 작업의 로그를 검토할 수 있어요. 로그 확인에 대한 자세한 내용은 워크플로 실행 로그 사용하기를 참고하세요.