Custom executor와 libvirt 사용하기
libvirt를 사용하면 Custom executor 드라이버가 각 작업을 실행할 때마다 새 디스크와 VM을 만든 뒤 해당 디스크와 VM을 삭제합니다.
이 예시는 커뮤니티의 기여를 바탕으로 만들어졌습니다. !464에서 libvirt를 GitLab Runner executor로 추가하는 것을 참고했습니다.
본 문서에서는 libvirt 설정 방법을 설명하지 않습니다. 왜냐하면 이것은 본 문서의 범위를 벗어나기 때문입니다. 그러나 해당 드라이버는 GCP 이중 가상화를 사용하여 테스트되었으며, 해당 페이지에서는 libvirt 설정 방법에 대한 자세한 내용을 제공합니다. 본 예시에서는 libvirt 설치 시 함께 제공되는 default
네트워크를 사용하므로 해당 네트워크가 실행 중임을 확인하세요.
본 드라이버는 각 VM이 별도의 IP 주소를 가져야 하므로 가상 브릿지 네트워크가 필요합니다. 이를 통해 GitLab Runner가 해당 VM에 SSH하여 명령을 실행할 수 있습니다. SSH 키는 다음 명령을 사용하여 생성할 수 있습니다(https://docs.gitlab.com/ee/user/ssh.html#generate-an-ssh-key-pair).
각 빌드에서 의존성을 다운로드하지 않도록 기본 디스크 VM 이미지가 생성됩니다. 아래 예시에서는 virt-builder를 사용하여 디스크 VM 이미지를 만드는 방법을 보여줍니다.
virt-builder debian-11 \
--size 8G \
--output /var/lib/libvirt/images/gitlab-runner-base.qcow2 \
--format qcow2 \
--hostname gitlab-runner-bullseye \
--network \
--install curl \
--run-command 'curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | bash' \
--run-command 'curl -s "https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh" | bash' \
--run-command 'useradd -m -p "" gitlab-runner -s /bin/bash' \
--install gitlab-runner,git,git-lfs,openssh-server \
--run-command "git lfs install --skip-repo" \
--ssh-inject gitlab-runner:file:/root/.ssh/id_rsa.pub \
--run-command "echo 'gitlab-runner ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers" \
--run-command "sed -E 's/GRUB_CMDLINE_LINUX=\"\"/GRUB_CMDLINE_LINUX=\"net.ifnames=0 biosdevname=0\"/' -i /etc/default/grub" \
--run-command "grub-mkconfig -o /boot/grub/grub.cfg" \
--run-command "echo 'auto eth0' >> /etc/network/interfaces" \
--run-command "echo 'allow-hotplug eth0' >> /etc/network/interfaces" \
--run-command "echo 'iface eth0 inet dhcp' >> /etc/network/interfaces"
위 명령은 이전에 명시한 필수 설치 사항을 모두 설치할 것입니다.
virt-builder
는 자동으로 루트 암호를 설정하며, 이는 마지막 부분에 출력됩니다. 직접 암호를 지정하려면 --root-password password:$SOME_PASSWORD
를 전달하세요.
구성
다음은 libvirt를 위한 GitLab Runner 구성의 예시입니다.
concurrent = 1
check_interval = 0
[session_server]
session_timeout = 1800
[[runners]]
name = "libvirt-driver"
url = "https://gitlab.com/"
token = "xxxxx"
executor = "custom"
builds_dir = "/home/gitlab-runner/builds"
cache_dir = "/home/gitlab-runner/cache"
[runners.custom_build_dir]
[runners.cache]
[runners.cache.s3]
[runners.cache.gcs]
[runners.custom]
prepare_exec = "/opt/libvirt-driver/prepare.sh" # VM을 생성하기 위한 bash 스크립트 경로
run_exec = "/opt/libvirt-driver/run.sh" # VM 내부의 스크립트를 SSH로 실행하기 위한 bash 스크립트 경로
cleanup_exec = "/opt/libvirt-driver/cleanup.sh" # VM 및 디스크를 삭제하기 위한 bash 스크립트 경로
기본
각 stage(prepare, run, cleanup)은 다른 스크립트에서 사용되는 변수를 생성하기 위해 아래 기본 스크립트를 사용합니다.
이 스크립트가 다른 스크립트와 동일한 디렉토리에 위치해야 하므로 주의하세요. 여기서는 /opt/libivirt-driver/
에 위치하도록 되어 있습니다.
#!/usr/bin/env bash
# /opt/libvirt-driver/base.sh
VM_IMAGES_PATH="/var/lib/libvirt/images"
BASE_VM_IMAGE="$VM_IMAGES_PATH/gitlab-runner-base.qcow2"
VM_ID="runner-$CUSTOM_ENV_CI_RUNNER_ID-project-$CUSTOM_ENV_CI_PROJECT_ID-concurrent-$CUSTOM_ENV_CI_CONCURRENT_PROJECT_ID-job-$CUSTOM_ENV_CI_JOB_ID"
VM_IMAGE="$VM_IMAGES_PATH/$VM_ID.qcow2"
_get_vm_ip() {
virsh -q domifaddr "$VM_ID" | awk '{print $4}' | sed -E 's|/([0-9]+)?$||'
}
준비
준비 스크립트는 다음을 수행합니다.
- 디스크를 새 경로로 복사합니다.
- 복사된 디스크에서 새 가상 머신을 설치합니다.
- 가상 머신이 IP를 가져오기를 기다립니다.
- VM에서 SSH가 응답할 때까지 기다립니다.
#!/usr/bin/env bash
# /opt/libvirt-driver/prepare.sh
currentDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
source ${currentDir}/base.sh # 기본 스크립트에서 변수 가져오기.
set -eo pipefail
# 모든 오류를 catch하고 시스템 장애로 표시합니다.
trap "exit $SYSTEM_FAILURE_EXIT_CODE" ERR
# Job에 사용할 기본 디스크를 복사합니다.
qemu-img create -f qcow2 -b "$BASE_VM_IMAGE" "$VM_IMAGE" -F qcow2
# VM 설치
virt-install \
--name "$VM_ID" \
--os-variant debian11 \
--disk "$VM_IMAGE" \
--import \
--vcpus=2 \
--ram=2048 \
--network default \
--graphics none \
--noautoconsole
# VM이 IP를 가져올 때까지 기다립니다.
echo 'VM가 IP를 가져오기를 기다리는 중'
for i in $(seq 1 30); do
VM_IP=$(_get_vm_ip)
if [ -n "$VM_IP" ]; then
echo "VM이 IP를 가져왔습니다: $VM_IP"
break
fi
if [ "$i" == "30" ]; then
echo 'VM이 시작되기까지 30초 기다렸으나 종료합니다...'
# 이것이 시스템 장애임을 GitLab Runner에 알리기 위해
# 종료되어야 합니다.
exit "$SYSTEM_FAILURE_EXIT_CODE"
fi
sleep 1s
done
# ssh 사용 가능 여부를 기다립니다.
echo "sshd 사용 가능 여부를 기다리는 중"
for i in $(seq 1 30); do
if ssh -i /root/.ssh/id_rsa -o StrictHostKeyChecking=no gitlab-runner@"$VM_IP" >/dev/null 2>/dev/null; then
break
fi
if [ "$i" == "30" ]; then
echo 'sshd가 시작되기까지 30초 기다렸으나 종료합니다...'
# 이것이 시스템 장애임을 GitLab Runner에 알리기 위해
# 종료되어야 합니다.
exit "$SYSTEM_FAILURE_EXIT_CODE"
fi
sleep 1s
done
실행
이 명령은 GitLab Runner에 의해 생성된 스크립트를 SSH를 통해 VM으로 STDIN
을 통해 전송하여 실행합니다.
#!/usr/bin/env bash
# /opt/libvirt-driver/run.sh
currentDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
source ${currentDir}/base.sh # 기본 스크립트에서 변수 가져오기.
VM_IP=$(_get_vm_ip)
ssh -i /root/.ssh/id_rsa -o StrictHostKeyChecking=no gitlab-runner@"$VM_IP" /bin/bash < "${1}"
if [ $? -ne 0 ]; then
# 빌드를 GitLab에서 실패로 표시하기 위해 변수를 사용하여 종료합니다.
exit "$BUILD_FAILURE_EXIT_CODE"
fi
정리
이 스크립트는 VM을 제거하고 디스크를 삭제합니다.
#!/usr/bin/env bash
# /opt/libvirt-driver/cleanup.sh
currentDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
source ${currentDir}/base.sh # 기본 스크립트에서 변수 가져오기.
set -eo pipefail
# VM 종료.
virsh shutdown "$VM_ID"
# VM 정의 삭제.
virsh undefine "$VM_ID"
# VM 디스크 삭제.
if [ -f "$VM_IMAGE" ]; then
rm "$VM_IMAGE"
fi