1
votes

I have dotnet solution which consists of console project, webapi project and mysql db. I put them in separate docker images, wrote docker-compose to start and tested on my machine. Next, I wrote a test using FluentDocker, which allows me to start docker-compose programmatically and verify that containers are up and running.

Now I want to do this on Gitlab CI. Previously I used image: mcr.microsoft.com/dotnet/core/sdk:3.1 and run test stage against test project. It worked fine because there was no docker integration. I cant run FluentDocker test on Gitlab CI because the image does not contain docker. So I started researching.

  1. Solution to incorporate db in CI job is here https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service , but I doubt that I can use docker as service.

  2. Next is using docker intergration for gitlab runner https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-in-docker-workflow-with-docker-executor or https://tomgregory.com/running-docker-in-docker-on-windows/ I cant use that because I use free runner from Gitlab itself and cant configure it. I tried to run docker info command, it fails in my script.

  3. I thought of building my own image based on dotnet sdk with docker included, but it seems like a bad idea. And I did not make it working in the end.

  4. Solution that seems working is to use dind and start docker-compose: How to run docker-compose inside docker in docker which runs inside gitlab-runner container? or https://gitlab.com/gitlab-org/gitlab-foss/-/issues/30426 But to use it I will need to install dotnet-sdk to build and test my app in before-script section, and sdk is not a small package to download each time.

  5. I can try to build my image based on docker:dind and include dotnet sdk there, publish it on dockerhub then use it in gitlab runner. Now it seems to me as the last option.

So, whats the correct approach here?

-----------edit--------------

I made this working! See the very thorough answer from Konrad Botor with dockerfile and yml file. I built my own image with sdk and docker and used it for test stage with dind service linked. My image is hosted on dockerhub, so gitlab downloads it for usage.

Also some notes:

1 - how to use dind as service https://gitlab.com/gitlab-org/gitlab-runner/-/issues/25344

2 - where to get modprobe.sh and docker-entrypoint.sh https://github.com/docker-library/docker (go inside latest release). Very important is to clone repo and copy files from there, because I tried to copy-paste contents and it did not work.

3 - docker-compose repo https://github.com/tmaier/docker-compose/blob/master/Dockerfile

4 - dind example https://gitlab.com/gitlab-examples/docker/-/blob/master/.gitlab-ci.yml

2
Actually copy-pasting the contents of the *.sh files works fine if you remember to save them with LF line endings. Also could you accept my answer? - Konrad Botor

2 Answers

1
votes

Canonical way to use docker in GitLab CI is shown in GitLab examples repo here. I assume it works with free runner, since it's the official group maintained by GitLab itself.

Since your job requires more than just Docker, I would suggest using custom image with Dotnet SDK, Docker and docker-compose, like so:

FROM mcr.microsoft.com/dotnet/core/sdk:3.1-alpine

RUN apk add --no-cache \
        ca-certificates \
# DOCKER_HOST=ssh://... -- https://github.com/docker/cli/pull/1014
        openssh-client

# set up nsswitch.conf for Go's "netgo" implementation (which Docker explicitly uses)
# - https://github.com/docker/docker-ce/blob/v17.09.0-ce/components/engine/hack/make.sh#L149
# - https://github.com/golang/go/blob/go1.9.1/src/net/conf.go#L194-L275
# - docker run --rm debian:stretch grep '^hosts:' /etc/nsswitch.conf
RUN [ ! -e /etc/nsswitch.conf ] && echo 'hosts: files dns' > /etc/nsswitch.conf

ENV DOCKER_CHANNEL stable
ENV DOCKER_VERSION 19.03.12
# TODO ENV DOCKER_SHA256
# https://github.com/docker/docker-ce/blob/5b073ee2cf564edee5adca05eee574142f7627bb/components/packaging/static/hash_files !!
# (no SHA file artifacts on download.docker.com yet as of 2017-06-07 though)

RUN set -eux; \
    \
# this "case" statement is generated via "update.sh"
    apkArch="$(apk --print-arch)"; \
    case "$apkArch" in \
# amd64
        x86_64) dockerArch='x86_64' ;; \
# arm32v6
        armhf) dockerArch='armel' ;; \
# arm32v7
        armv7) dockerArch='armhf' ;; \
# arm64v8
        aarch64) dockerArch='aarch64' ;; \
        *) echo >&2 "error: unsupported architecture ($apkArch)"; exit 1 ;;\
    esac; \
    \
    if ! wget -O docker.tgz "https://download.docker.com/linux/static/${DOCKER_CHANNEL}/${dockerArch}/docker-${DOCKER_VERSION}.tgz"; then \
        echo >&2 "error: failed to download 'docker-${DOCKER_VERSION}' from '${DOCKER_CHANNEL}' for '${dockerArch}'"; \
        exit 1; \
    fi; \
    \
    tar --extract \
        --file docker.tgz \
        --strip-components 1 \
        --directory /usr/local/bin/ \
    ; \
    rm docker.tgz; \
    \
    dockerd --version; \
    docker --version

COPY modprobe.sh /usr/local/bin/modprobe
COPY docker-entrypoint.sh /usr/local/bin/

# https://github.com/docker-library/docker/pull/166
#   dockerd-entrypoint.sh uses DOCKER_TLS_CERTDIR for auto-generating TLS certificates
#   docker-entrypoint.sh uses DOCKER_TLS_CERTDIR for auto-setting DOCKER_TLS_VERIFY and DOCKER_CERT_PATH
# (For this to work, at least the "client" subdirectory of this path needs to be shared between the client and server containers via a volume, "docker cp", or other means of data sharing.)
ENV DOCKER_TLS_CERTDIR=/certs
# also, ensure the directory pre-exists and has wide enough permissions for "dockerd-entrypoint.sh" to create subdirectories, even when run in "rootless" mode
RUN mkdir /certs /certs/client && chmod 1777 /certs /certs/client
# (doing both /certs and /certs/client so that if Docker does a "copy-up" into a volume defined on /certs/client, it will "do the right thing" by default in a way that still works for rootless users)

ENV COMPOSE_VERSION 1.26.2

RUN apk add --no-cache py3-pip python3
RUN apk add --no-cache --virtual build-dependencies python3-dev libffi-dev openssl-dev gcc libc-dev make \
  && pip3 install "docker-compose${COMPOSE_VERSION:+==}${COMPOSE_VERSION}" \
  && apk del build-dependencies

ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["sh"]

This is based on official docker:latest Dockerfile as well as tmaier/docker-compose:latest Dockerfile. You would also need modprobe.sh and docker-entrypoint.sh scripts from the first repo.

Then run it as follows:

image: docker:latest //whatever image you use in your pipeline most often

variables:
  DOCKER_DRIVER: overlay2

stages:
- test

test:
  stage: test
  image: dotnetsdk-compose:latest //your custom image
  services:
  - docker:dind
  script:
    - docker-compose up -d

Note that the new image is based on Alpine 3.12 rather than Debian 10. If it's an issue you would have to convert the above Dockerfile to use commands available on Debian.

0
votes

We have the same situation in our comapny but with a self hosted docker / gitlab etc.

Currently we are running docker integration in gitlab runner (your number 2) and had a bunch of bad side-effects e.g.: that the container created by the gitlab runner isnt cleaned up properly because gitlab-runner doesnt think about cleaning up, because in normal cases with the closing of the docker-runner itself it should (wihtout) dnd destroying everything.

Option 3 is not a bad thing, its a default and complete normal thing to do: we have a bunch of docker images that includes lib's that are not given withtin the loaded image. You get used to it - so I would prefer that. Is a clean solution which doesnt feel "hacky".