github action을 통해서 Unit test를 하는데 GPU가 필요해서, 그냥 가지고 있는 우분투 서버를 이용하기로 함. 이 글은 이미 repo에 github action이 한 개 이상 설정되어 있음을 가정함. 만약 설정이 안되어 있다면 링크 를 읽어보고 하기 바람(설정이 안되어 있다면 github UI가 약간 다름)
1. github repository에서 "Actions" -> "New workflow" 를 클릭하고 yml 에 아래 내용을 입력
name: Testing
on: push
jobs:
testing:
runs-on: self-hosted
steps:
- uses: actions/checkout@v2
- name: Functionality test
shell: bash -l {0}
run: make action_test # 이 자리에 unit test 실행 코드가 들어 있으면 됨
2. github action을 실행할 docker container 이미지 파일을 빌드해야한다. unit test 환경이 바뀔 수 있으니(library 등) 일단 unit test 용 base image를 빌드하고, base image 위에 종속 라이브러리를 설치한 docker image를 한개 더 빌드한다. base image 를 위한 Dockerfile은 아래와 같고, 첫번째 줄 이외에는 수정하지 않는다.
FROM ubuntu # 특별히 사용하는 base image가 있다면 변경함. 나머지 아래는 건들지 않는다.
ARG GIT_VERSION="2.29.0"
ARG DUMB_INIT_VERSION="1.2.2"
ARG DOCKER_KEY="7EA0A9C3F273FCD8"
ENV DOCKER_COMPOSE_VERSION="1.27.4"
ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US:en
ENV LC_ALL=en_US.UTF-8
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
ENV DEBIAN_FRONTEND=noninteractive
# hadolint ignore=DL3003,DL4001
RUN apt-get update && \
apt-get install -y --no-install-recommends \
awscli \
curl \
tar \
unzip \
apt-transport-https \
ca-certificates \
sudo \
gnupg-agent \
software-properties-common \
build-essential \
zlib1g-dev \
gettext \
liblttng-ust0 \
libcurl4-openssl-dev \
inetutils-ping \
jq \
wget \
dirmngr \
openssh-client \
locales \
python3-pip \
&& pip3 install --no-cache-dir awscliv2 \
&& locale-gen en_US.UTF-8 \
&& dpkg-reconfigure locales \
&& c_rehash \
&& cd /tmp \
&& [[ $(lsb_release -cs) == "xenial" ]] && ( wget --quiet "https://github.com/Yelp/dumb-init/releases/download/v${DUMB_INIT_VERSION}/dumb-init_${DUMB_INIT_VERSION}_$(uname -i | sed 's/x86_64/amd64/g').deb" && dpkg -i dumb-init_*.deb && rm dumb-init_*.deb ) || ( apt-get install -y --no-install-recommends dumb-init ) \
&& curl -sL https://www.kernel.org/pub/software/scm/git/git-${GIT_VERSION}.tar.gz -o git.tgz \
&& tar zxf git.tgz \
&& cd git-${GIT_VERSION} \
&& ./configure --prefix=/usr \
&& make \
&& make install \
&& cd / \
&& apt-key adv --keyserver keyserver.ubuntu.com --recv-keys ${DOCKER_KEY} \
&& curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - \
&& [[ $(lsb_release -cs) == "focal" ]] && ( add-apt-repository "deb [arch=$(dpkg --print-architecture)] https://download.docker.com/linux/ubuntu focal stable" ) || ( add-apt-repository "deb [arch=$(dpkg --print-architecture)] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" ) \
&& apt-get update \
&& apt-get install -y docker-ce docker-ce-cli containerd.io --no-install-recommends --allow-unauthenticated \
&& curl -sL "https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose \
&& chmod +x /usr/local/bin/docker-compose \
&& rm -rf /var/lib/apt/lists/* \
&& rm -rf /tmp/*
3. unit test 용 base image를 위한 Dockerfile을 작성했다. 이 파일을 Dockerfile.base라고 저장한 경우 아래 커맨드로 docker image를 build 한다. 아래 커맨드는 repo 의 최상단 디렉토리에서 실행한다.
# docker build -f {docker file path} -t {docker image name with tag} .
docker build -f Dockerfile.base -t github_action:base .
4. 이제 실제로 unit test를 실행하는 Dockerfile을 작성한다.
FROM github_action:base
COPY requirements.txt /tmp/ # repo에 종속 라이브러리를 명시한 파일을 복사해옴.
RUN apt-get update &&\
apt-get -y install # unit test를 위해 설치가 필요한 것들을 여기 적어주자, 없다면 apt-get은 생략
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py &&\
python3 get-pip.py &&\ # 내 경우 pip가 정상작동하지 않는 경우가 있어서 pip만 한번 더 재설치 함
pip3 install -U pip &&\
pip3 install --ignore-installed --no-cache-dir -r /tmp/requirements.txt &&\
rm -rf /var/lib/apt/lists/* /tmp/requirements.txt &&\
ENV AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache
RUN mkdir -p /opt/hostedtoolcache
ARG GH_RUNNER_VERSION="2.277.1"
ARG TARGETPLATFORM
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
WORKDIR /actions-runner
COPY .github/Dockerfiles/install_actions.sh /actions-runner
RUN chmod +x /actions-runner/install_actions.sh \
&& /actions-runner/install_actions.sh ${GH_RUNNER_VERSION} ${TARGETPLATFORM} \
&& rm /actions-runner/install_actions.sh
COPY .github/Dockerfiles/token.sh /
RUN chmod +x /token.sh
COPY .github/Dockerfiles/entrypoint.sh /
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
5. 위 파일에서 .github/Dockerfiles/ 밑에 있는 "token.sh", "entrypoint.sh", "install_actions.sh" 파일들을 언급하고 있는데,
https://github.com/myoung34/docker-github-actions-runner
myoung34/docker-github-actions-runner
This will run the new self-hosted github actions runners with docker-in-docker - myoung34/docker-github-actions-runner
github.com
여기서 같은 이름의 파일을 찾아서 그대로 복사 붙여넣기하여 repo의 .github/Dockerfiles/ 밑에 작성 후 저장한다. 내 경우는 entrypoint.sh 만 일부 수정해서 사용함.
# entrypoint.sh
#!/usr/bin/dumb-init /bin/bash
export RUNNER_ALLOW_RUNASROOT=1
export PATH=$PATH:/actions-runner
deregister_runner() {
echo "Caught SIGTERM. Deregistering runner"
_TOKEN=$(bash /token.sh)
RUNNER_TOKEN=$(echo "${_TOKEN}" | jq -r .token)
./config.sh remove --token "${RUNNER_TOKEN}"
exit
}
_RUNNER_NAME=${RUNNER_NAME:-${RUNNER_NAME_PREFIX:-github-runner}-$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13 ; echo '')}
_RUNNER_WORKDIR=${RUNNER_WORKDIR:-/_work}
_LABELS=${LABELS:-default}
_RUNNER_GROUP=${RUNNER_GROUP:-Default}
_SHORT_URL=${REPO_URL}
_GITHUB_HOST=${GITHUB_HOST:="github.com"}
if [[ ${ORG_RUNNER} == "true" ]]; then
_SHORT_URL="https://${_GITHUB_HOST}/${ORG_NAME}"
fi
if [[ -n "${ACCESS_TOKEN}" ]]; then
_TOKEN=$(bash /token.sh)
RUNNER_TOKEN=$(echo "${_TOKEN}" | jq -r .token)
_SHORT_URL=$(echo "${_TOKEN}" | jq -r .short_url)
fi
echo "Configuring"
./config.sh \
--url "${_SHORT_URL}" \
--token "${RUNNER_TOKEN}" \
--name "${_RUNNER_NAME}" \
--work "${_RUNNER_WORKDIR}" \
--labels "${_LABELS}" \
--runnergroup "${_RUNNER_GROUP}" \
--unattended \
--replace
unset RUNNER_TOKEN
trap deregister_runner SIGINT SIGQUIT SIGTERM
./bin/runsvc.sh
6. github token 만들기
깃헙 계정의 settings → Developer settings → personal access tokens 에서 "workflow" 에 체크 후 생성 -> 생성된 키를 복사
7. self hosted runner 가 실행될 서버에서 docker container를 아래 커맨드로 띄운다. 이때 6번에서 복사해둔 키를 ACCESS_TOKEN에 입력해서 사용한다.
docker run --gpus 0 -e NVIDIA_VISIBLE_DEVICES=1 -d --restart always --name github-runner \
-e RUNNER_NAME="myrunner" \
-e REPO_URL="{your github repo url}"
-e ACCESS_TOKEN="{your github token}" \
-e RUNNER_WORKDIR="/tmp/{repo name}" \
-e RUNNER_GROUP="my-group" \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /tmp/{repo name}:/tmp/{repo name} \
{docker image name}
끝.