├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── base.sh ├── cleanup.sh ├── custom_base.template.sh ├── prepare.sh └── run.sh /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "Check Script Files" 2 | on: 3 | pull_request: 4 | push: 5 | 6 | jobs: 7 | check_script_files: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: "Shellcheck" 13 | run: shellcheck --check-sourced *.sh 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | custom_base.sh 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015-2019 GitLab B.V., 2020 Jonas Bushart 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Using Podman to power your Gitlab CI pipeline 2 | 3 | **⚠️ NOTE ⚠️**: New deployments should avoid using code from this repository. Instead the official Podman support should be used . 4 | Old deployments should consider migrating if possible. 5 | 6 | 1. [Installation and Setup](#installation-and-setup) 7 | 1. [Set up rootless Podman for the gitlab-runner user](#set-up-rootless-podman-for-the-gitlab-runner-user) 8 | 2. [Installing the gitlab-runner](#installing-the-gitlab-runner) 9 | 3. [Setting up a Runner Instance](#setting-up-a-runner-instance) 10 | 2. [Tweaking the Installation](#tweaking-the-installation) 11 | 1. [Private Registries](#private-registries) 12 | 3. [License](#license) 13 | 4. [Links](#links) 14 | 15 | ## Installation and Setup 16 | 17 | The install instructions are for a Fedora 31+ installation. 18 | Most of the instructions should transfer to other distributions. 19 | gitlab-runner needs to be installed in version 12.6 or higher, because we rely on the `image` tag being exposed from the `.gitlab-ci.yml` file. 20 | 21 | ### Set up rootless Podman for the gitlab-runner user 22 | 23 | Make sure you have added entries in `/etc/subuid` and `/etc/subgid` for the gitlab-runner user. 24 | Enable lingering for the gitlab-runner user with `sudo loginctl enable-linger gitlab-runner`. 25 | Run `sudo -iu gitlab-runner podman system migrate` to set correct cgroups behavior and silence a warning during job execution. 26 | 27 | ### Installing the gitlab-runner 28 | 29 | First, you need to install the [gitlab-runner][gitlab-runner-install] using the instructions listed on the website. 30 | You can silence the SELinux warnings, by labelling the binary with the proper `bin_t` type like: 31 | 32 | ```bash 33 | sudo chcon -t bin_t /usr/bin/gitlab-runner 34 | ``` 35 | 36 | Ensure that the gitlab-runner service runs with the appropirate permissions. 37 | Since we are using Podman in a rootless setup, we can run the service with user privileges instead of root permissions. 38 | Add a systemd dropin (`/etc/systemd/system/gitlab-runner.service.d/rootless.conf`): 39 | 40 | ```ini 41 | [Service] 42 | User=gitlab-runner 43 | Group=gitlab-runner 44 | ``` 45 | 46 | ### Setting up a Runner Instance 47 | 48 | As the gitlab-runner user change into the home directory (`/home/gitlab-runner`) and clone this repository. 49 | 50 | ```bash 51 | git clone https://github.com/jonasbb/podman-gitlab-runner 52 | ``` 53 | 54 | Then follow the [instructions][gitlab-runner-register] to set up a new runner instance: 55 | 56 | ```bash 57 | sudo -u gitlab-runner gitlab-runner register \ 58 | --url https://my.gitlab.instance/ \ 59 | --registration-token $GITLAB_REGISTRATION_TOKEN \ 60 | --name "Podman Runner" \ 61 | --executor custom \ 62 | --builds-dir /home/user \ 63 | --cache-dir /home/user/cache \ 64 | --custom-prepare-exec "/home/gitlab-runner/podman-gitlab-runner/prepare.sh" \ 65 | --custom-run-exec "/home/gitlab-runner/podman-gitlab-runner/run.sh" \ 66 | --custom-cleanup-exec "/home/gitlab-runner/podman-gitlab-runner/cleanup.sh" 67 | ``` 68 | 69 | ## Tweaking the Installation 70 | 71 | Currently, the scripts do not provide much customization. 72 | However, you can adapt the functions `start_container` and `install_dependencies` to specify how Podman should spawn the containers and how to install the dependencies. 73 | 74 | Some behaviour can be tweaked by tweaked by setting the correct environment variables. 75 | Rename the `custom_base.template.sh` file into `custom_base.sh` to make use of the customization. 76 | The following variables are supported right now: 77 | 78 | * `PODMAN_RUN_ARGS`: Customize how Podman spawns the containers. 79 | 80 | ### Private Registries 81 | 82 | Podman supports access to private registries. 83 | You can set the `DOCKER_AUTH_CONFIG` variable under **Settings → CI / CD** and provide the credentials for accessing the private registry. 84 | Details how the variable has to look can be found under [using statically defined credentials][gitlab-static-credentials] in the Gitlab documentation. 85 | 86 | Additionally, there are multiple ways to authenticate against Gitlab Registries. 87 | The script uses a configured deploy token (via `$CI_DEPLOY_PASSWORD`) to login. 88 | Alternatively, the CI job also provides access to the registry for the duraion of a single job. 89 | The scipt uses variables `$CI_JOB_TOKEN` and `$CI_REGISTRY_PASSWORD`, if available, to log into the registry. 90 | 91 | The four methods are tried in order until one succeeds: 92 | 93 | 1. `DOCKER_AUTH_CONFIG` 94 | 2. `CI_DEPLOY_PASSWORD` 95 | 3. `CI_JOB_TOKEN` 96 | 4. `CI_REGISTRY_PASSWORD` 97 | 98 | More details about different authentication variants in the official documentation: 99 | 100 | ## Using Podman in Podman containers 101 | 102 | Executing Podman inside is useful to test containers or build new images inside the CI. 103 | By default the nesting fails, since access to the overlayfs is not possible. 104 | 105 | RedHat has a guide how to run Podman inside of Podman containers in both rootful and rootless scenarios: 106 | 107 | 108 | ## License 109 | 110 | Licensed under the [MIT license]. 111 | 112 | ## Links 113 | 114 | * 115 | First source describing how to set up Podman and gitlab-runner and the source for these scripts. 116 | * 117 | Official documentation about the custom executor feature for Gitlab CI. 118 | * 119 | Official examples how to use the custom executor feature. 120 | * 121 | Alternative implementation of a Podman executor. 122 | 123 | [gitlab-runner-install]: https://docs.gitlab.com/runner/install/linux-repository.html 124 | [gitlab-runner-register]: https://docs.gitlab.com/runner/register/ 125 | [gitlab-static-credentials]: https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#using-statically-defined-credentials 126 | [MIT license]: LICENSE 127 | -------------------------------------------------------------------------------- /base.sh: -------------------------------------------------------------------------------- 1 | # shellcheck shell=bash disable=SC2034 2 | 3 | currentDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" 4 | 5 | # Variables defined here will be availble to all the scripts. 6 | 7 | CONTAINER_ID="runner-$CUSTOM_ENV_CI_RUNNER_ID-project-$CUSTOM_ENV_CI_PROJECT_ID-concurrent-$CUSTOM_ENV_CI_CONCURRENT_PROJECT_ID-$CUSTOM_ENV_CI_JOB_ID" 8 | IMAGE="$CUSTOM_ENV_CI_JOB_IMAGE" 9 | CACHE_DIR="$(dirname "${BASH_SOURCE[0]}")/../_cache/runner-$CUSTOM_ENV_CI_RUNNER_ID-project-$CUSTOM_ENV_CI_PROJECT_ID-concurrent-$CUSTOM_ENV_CI_CONCURRENT_PROJECT_ID" 10 | 11 | # Execute customization code 12 | if [ -f "${currentDir}"/custom_base.sh ]; then 13 | # shellcheck source=custom_base.sh disable=SC1091 14 | source "${currentDir}"/custom_base.sh 15 | fi 16 | -------------------------------------------------------------------------------- /cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | TMPDIR=$(pwd) 4 | 5 | currentDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" 6 | # shellcheck source=base.sh 7 | source "${currentDir}"/base.sh 8 | 9 | echo "Deleting container $CONTAINER_ID" 10 | 11 | podman kill "$CONTAINER_ID" 12 | podman rm "$CONTAINER_ID" 13 | 14 | # Try to remove all old containers, images, networks, and volumes 15 | podman system prune --force --volumes 16 | 17 | # Delete leftover files in /tmp 18 | rm -r "$TMPDIR" 19 | 20 | exit 0 21 | -------------------------------------------------------------------------------- /custom_base.template.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Rename this file to `custom_base.sh`! 4 | # 5 | # The file contains examples how the different variables can be used. 6 | 7 | # Pass additional arguments to `podman run` in `prepare.sh`. 8 | # Mount additional volumes into the container or limit the CPU utilization. 9 | # PODMAN_RUN_ARGS[0]='--volume=/mnt:/path/in/container:z' 10 | # PODMAN_RUN_ARGS[1]='--cpus=1' 11 | 12 | # Change the command given to `podman run` in `prepare.sh`. 13 | # This command *must* not return or the container could stop before the 14 | # tasks are done. The default is to call `sleep` with a very long time. 15 | # PODMAN_RUN_COMMAND=/sbin/init 16 | -------------------------------------------------------------------------------- /prepare.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | currentDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" 4 | # shellcheck source=base.sh 5 | source "${currentDir}"/base.sh 6 | 7 | set -eEo pipefail 8 | 9 | # trap any error, and mark it as a system failure. 10 | trap 'exit $SYSTEM_FAILURE_EXIT_CODE' ERR 11 | 12 | start_container() { 13 | if podman inspect "$CONTAINER_ID" >/dev/null 2>&1; then 14 | echo 'Found old container, deleting' 15 | podman kill "$CONTAINER_ID" 16 | podman rm "$CONTAINER_ID" 17 | fi 18 | 19 | mkdir -p "$CACHE_DIR" 20 | # Use value of ENV variable or {} as empty settings 21 | echo "${CUSTOM_ENV_DOCKER_AUTH_CONFIG:-{\}}" > "$CACHE_DIR"/_authfile_"$CONTAINER_ID" 22 | 23 | # Try logging into the Gitlab Registry if credentials are provided 24 | # https://docs.gitlab.com/ee/user/packages/container_registry/index.html#authenticate-by-using-gitlab-cicd 25 | if [[ -n "$CUSTOM_ENV_CI_REGISTRY" ]] 26 | then 27 | if ! podman login --authfile "$CACHE_DIR"/_authfile_"$CONTAINER_ID" --get-login "$CUSTOM_ENV_CI_REGISTRY" 2>/dev/null && \ 28 | [[ -n "$CUSTOM_ENV_CI_DEPLOY_USER" && -n "$CUSTOM_ENV_CI_DEPLOY_PASSWORD" ]] 29 | then 30 | echo "Login to ${CUSTOM_ENV_CI_REGISTRY} with CI_DEPLOY_USER" 31 | podman login --authfile "$CACHE_DIR"/_authfile_"$CONTAINER_ID" \ 32 | --username "$CUSTOM_ENV_CI_DEPLOY_USER" \ 33 | --password "$CUSTOM_ENV_CI_DEPLOY_PASSWORD" \ 34 | "$CUSTOM_ENV_CI_REGISTRY" 35 | fi 36 | 37 | if ! podman login --authfile "$CACHE_DIR"/_authfile_"$CONTAINER_ID" --get-login "$CUSTOM_ENV_CI_REGISTRY" 2>/dev/null && \ 38 | [[ -n "$CUSTOM_ENV_CI_JOB_USER" && -n "$CUSTOM_ENV_CI_JOB_TOKEN" ]] 39 | then 40 | echo "Login to ${CUSTOM_ENV_CI_REGISTRY} with CI_JOB_USER" 41 | podman login --authfile "$CACHE_DIR"/_authfile_"$CONTAINER_ID" \ 42 | --username "$CUSTOM_ENV_CI_JOB_USER" \ 43 | --password "$CUSTOM_ENV_CI_JOB_TOKEN" \ 44 | "$CUSTOM_ENV_CI_REGISTRY" 45 | fi 46 | 47 | if ! podman login --authfile "$CACHE_DIR"/_authfile_"$CONTAINER_ID" --get-login "$CUSTOM_ENV_CI_REGISTRY" 2>/dev/null && \ 48 | [[ -n "$CUSTOM_ENV_CI_REGISTRY_USER" && -n "$CUSTOM_ENV_CI_REGISTRY_PASSWORD" ]] 49 | then 50 | echo "Login to ${CUSTOM_ENV_CI_REGISTRY} with CI_REGISTRY_USER" 51 | podman login --authfile "$CACHE_DIR"/_authfile_"$CONTAINER_ID" \ 52 | --username "$CUSTOM_ENV_CI_REGISTRY_USER" \ 53 | --password "$CUSTOM_ENV_CI_REGISTRY_PASSWORD" \ 54 | "$CUSTOM_ENV_CI_REGISTRY" 55 | fi 56 | fi 57 | 58 | podman pull --authfile "$CACHE_DIR"/_authfile_"$CONTAINER_ID" "$IMAGE" 59 | rm "$CACHE_DIR"/_authfile_"$CONTAINER_ID" 60 | # We want shell splitting on PODMAN_RUN_COMMAND 61 | # shellcheck disable=2086 62 | podman run \ 63 | --detach \ 64 | --name "$CONTAINER_ID" \ 65 | --volume "$CACHE_DIR:/home/user/cache":z \ 66 | "${PODMAN_RUN_ARGS[@]}" \ 67 | "$IMAGE"\ 68 | ${PODMAN_RUN_COMMAND:-sleep 999999999} 69 | } 70 | 71 | install_command() { 72 | # Run test if this binary exists 73 | PACKAGE=$1 74 | TEST_BINARY=$PACKAGE 75 | 76 | podman exec --user root:root "$CONTAINER_ID" /bin/bash -c 'if ! type '"$TEST_BINARY"' >/dev/null 2>&1; then 77 | if type apt-get >/dev/null 2>&1; then 78 | echo "APT based distro without '"$TEST_BINARY"'" 79 | apt-get update && apt-get install --no-install-recommends --yes '"$PACKAGE"' 80 | elif type dnf >/dev/null 2>&1; then 81 | echo "DNF based distro without '"$TEST_BINARY"'" 82 | dnf install --setopt=install_weak_deps=False --assumeyes '"$PACKAGE"' 83 | elif type apk >/dev/null 2>&1; then 84 | echo "APK based distro without '"$TEST_BINARY"'" 85 | apk add '"$PACKAGE"' 86 | elif type yum >/dev/null 2>&1; then 87 | echo "YUM based distro without '"$TEST_BINARY"'" 88 | yum install --assumeyes '"$PACKAGE"' 89 | elif type pacman >/dev/null 2>&1; then 90 | echo "PACMAN based distro without '"$TEST_BINARY"'" 91 | pacman --sync --refresh --noconfirm '"$PACKAGE"' 92 | elif type zypper >/dev/null 2>&1; then 93 | echo "ZYPPER based distro without '"$TEST_BINARY"'" 94 | zypper install --no-confirm --no-recommends '"$PACKAGE"' 95 | fi 96 | fi' 97 | } 98 | 99 | install_dependencies() { 100 | # Copy gitlab-runner binary from the server into the container 101 | local RUNNER_BINARY 102 | local RUNNER_BINARY_PREFIX 103 | local RUNNER_BINARY_TMP 104 | 105 | # First check some predefined paths 106 | for RUNNER_BINARY_PREFIX in /usr{/local,}/bin 107 | do 108 | RUNNER_BINARY="${RUNNER_BINARY_PREFIX}/gitlab-runner" 109 | if [ -x "${RUNNER_BINARY}" ] 110 | then 111 | break 112 | fi 113 | done 114 | 115 | # If unsuccessful, check shell PATHs 116 | if [ ! -x "${RUNNER_BINARY}" ] 117 | then 118 | RUNNER_BINARY="$(type -p gitlab-runner || true)" 119 | fi 120 | 121 | # As a last resort, download binary... 122 | if [ ! -x "${RUNNER_BINARY}" ] 123 | then 124 | # ... to temporary directory 125 | RUNNER_BINARY_TMP="$(mktemp --directory --tmpdir="${CACHE_DIR}")" 126 | RUNNER_BINARY="${RUNNER_BINARY_TMP}/gitlab-runner" 127 | 128 | # Find local architecture to download correct binary 129 | # https://stackoverflow.com/questions/45125516/possible-values-for-uname-m/45125525#45125525 130 | local RUNNER_BINARY_ARCH 131 | local RUNNER_BINARY_URL 132 | case "$(uname -m)" in 133 | x86_64) 134 | RUNNER_BINARY_ARCH=amd64 135 | ;; 136 | arm) 137 | RUNNER_BINARY_ARCH=arm 138 | ;; 139 | i[36]86) 140 | RUNNER_BINARY_ARCH=386 141 | ;; 142 | aarch64|armv8l) 143 | RUNNER_BINARY_ARCH=arm64 144 | ;; 145 | s390*) 146 | RUNNER_BINARY_ARCH=s390x 147 | ;; 148 | ppc64le) 149 | RUNNER_BINARY_ARCH=ppc64le 150 | ;; 151 | esac 152 | # https://docs.gitlab.com/runner/install/linux-manually.html#install-1 153 | RUNNER_BINARY_URL="https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-${RUNNER_BINARY_ARCH}" 154 | 155 | # Errors during download should kill the script... 156 | curl --location --output "${RUNNER_BINARY}" "${RUNNER_BINARY_URL}" 157 | # ... otherwise, we now have a working gitlab-runner binary 158 | chmod +x "${RUNNER_BINARY}" 159 | fi 160 | 161 | podman cp --pause=false "${RUNNER_BINARY}" "$CONTAINER_ID":/usr/bin/gitlab-runner 162 | 163 | # Clean up if download directory was used 164 | if [ -d "${RUNNER_BINARY_TMP}" ] 165 | then 166 | rm -rf "${RUNNER_BINARY_TMP}" 167 | fi 168 | 169 | # Install bash in systems with APK (e.g., Alpine) 170 | podman exec --user root:root "$CONTAINER_ID" sh -c 'if ! type bash >/dev/null 2>&1 && type apk >/dev/null 2>&1 ; then echo "APK based distro without bash"; apk add bash; fi' 171 | 172 | install_command hostname 173 | install_command ca-certificates 174 | install_command git 175 | # Not available on all systems, e.g., Debian 9 or RHEL 7 176 | install_command git-lfs || true 177 | } 178 | 179 | echo "Running in $CONTAINER_ID" 180 | 181 | start_container 182 | install_dependencies 183 | 184 | # Create build folder such that unprivileged users have access 185 | podman exec --user root:root "$CONTAINER_ID" /bin/bash -c "mkdir -p '$CUSTOM_ENV_CI_BUILDS_DIR' && chmod -R 777 '$CUSTOM_ENV_CI_BUILDS_DIR'" 186 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | currentDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" 4 | # shellcheck source=base.sh 5 | source "${currentDir}"/base.sh 6 | 7 | if ! podman exec -i "$CONTAINER_ID" /bin/bash < "$1" 8 | then 9 | # Exit using the variable, to make the build as failure in GitLab CI. 10 | exit "$BUILD_FAILURE_EXIT_CODE" 11 | fi 12 | --------------------------------------------------------------------------------