작업이 사용하는 컨테이너 커스터마이징하기

작업이 사용하는 컨테이너 커스터마이징하기

자체 호스팅 러너가 작업을 위해 컨테이너를 호출하는 방식을 커스터마이징할 수 있어요. Kubernetes나 Podman을 통해 컨테이너를 관리하거나, 컨테이너를 호출하는 데 사용되는 docker run 또는 docker create 명령을 커스터마이징할 수 있어요.

출처: 문서

본문

Note

이 기능은 현재 공개 미리보기(public preview) 상태이며 변경될 수 있어요.

컨테이너 커스터마이징 정보

GitHub Actions는 워크플로 파일의 container: 문을 사용해 컨테이너 내에서 작업을 실행할 수 있게 해줘요. 자세한 내용은 컨테이너에서 작업 실행하기를 참고하세요. 컨테이너 기반 작업을 처리하려면 자체 호스팅 러너가 각 작업에 대한 컨테이너를 만들어요.

GitHub Actions는 자체 호스팅 러너가 컨테이너를 만들 방식을 커스터마이징할 수 있는 명령을 지원해요. 예를 들어 이 명령들을 사용해 Kubernetes나 Podman을 통해 컨테이너를 관리하고, 컨테이너를 호출하는 데 사용되는 docker run 또는 docker create 명령도 커스터마이징할 수 있어요. 커스터마이징 명령은 러너에 특정 환경 변수가 설정되면 자동으로 트리거되는 스크립트에 의해 실행돼요. 자세한 내용은 아래 커스터마이징 스크립트 트리거하기를 참고하세요.

이 커스터마이징은 Linux 기반 자체 호스팅 러너에서만 사용할 수 있으며, root 사용자 접근은 필요하지 않아요.

컨테이너 커스터마이징 명령

GitHub Actions는 컨테이너 커스터마이징을 위한 다음 명령을 포함해요:

이 커스터마이징 명령 각각은 자체 JSON 파일에 정의되어야 해요. 파일 이름은 확장자 .json을 붙여 명령 이름과 일치해야 해요. 예를 들어 prepare_job 명령은 prepare_job.json에 정의돼요. 이 JSON 파일들은 기본 index.js 스크립트의 일부로 자체 호스팅 러너에서 함께 실행돼요. 이 과정은 커스터마이징 스크립트 생성하기에서 더 자세히 설명돼요.

이 명령들은 아래에서 더 자세히 설명되는 구성 인자도 포함해요.

prepare_job

prepare_job 명령은 작업이 시작될 때 호출돼요. GitHub Actions는 작업의 모든 작업 또는 서비스 컨테이너를 전달해요. 작업에 서비스 또는 작업 컨테이너가 있으면 이 명령이 호출돼요.

GitHub Actions는 prepare_job 명령에서 다음 작업을 수행할 것으로 가정해요:

  • 필요한 경우 이전 작업의 것을 정리(prune)해요.
  • 필요한 경우 네트워크를 만들어요.
  • 작업 및 서비스 컨테이너를 풀해요.
  • 작업 컨테이너를 시작해요.
  • 서비스 컨테이너를 시작해요.
  • GitHub Actions가 필요로 하는 정보를 응답 파일에 기록해요:
    • 필수: 컨테이너가 alpine Linux 컨테이너인지 여부를 (isAlpine boolean으로) 명시해요.
    • 선택: 작업 컨텍스트에 설정하려는 모든 컨텍스트 필드. 그렇지 않으면 사용자가 사용할 수 없게 돼요. 자세한 내용은 컨텍스트 참조를 참고하세요.
  • 헬스 체크가 성공하고 작업/서비스 컨테이너가 시작되면 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

결과 스크립트는 테스트 목적으로 제공되며, 요구 사항에 적합한지 직접 판단해야 해요.

  1. actions/runner-container-hooks 저장소를 자체 호스팅 러너에 클론해요.

  2. examples/ 디렉토리에는 각각 자체 JSON 파일이 있는 기존 커스터마이징 명령이 포함되어 있어요. 이 예제들을 검토하고 나만의 커스터마이징 명령의 시작점으로 사용할 수 있어요.

    • prepare_job.json
    • run_script_step.json
    • run_container_step.json
  3. npm 패키지를 빌드해요. 이 명령들은 packages/docker/distpackages/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가 실행하는 스크립트에 대해 사용 가능한 타임아웃 설정이 없어요. 결과적으로 스크립트에 타임아웃 처리를 추가하는 것을 고려할 수 있어요.

워크플로 실행 로그 검토하기

스크립트가 실행되고 있는지 확인하려면 해당 작업의 로그를 검토할 수 있어요. 로그 확인에 대한 자세한 내용은 워크플로 실행 로그 사용하기를 참고하세요.