How can I communicate with my services in my gitlab job? - docker

I have the following gitlab job:
build-test-consumer-ui:
stage: build
services:
- name: postgres:11.2
alias: postgres
- name: my-repo/special-hasura
alias: graphql-engine
image: node:13.8.0-alpine
variables:
FF_NETWORK_PER_BUILD: 1
script:
- wget -q http://graphql-engine/v1/version
The docker image my-repo/special-hasura looks more or less like this:
FROM hasura/graphql-engine:v1.3.3.cli-migrations
ENV HASURA_GRAPHQL_DATABASE_URL="postgres://postgres:#postgres:/postgres"
ENV HASURA_GRAPHQL_ENABLED_LOG_TYPES="startup, http-log, webhook-log, websocket-log, query-log"
COPY ./my-migrations /hasura-migrations
EXPOSE 8080
When I run my gitlab job, then I see that my hasura instance initializes properly, i.e. it can connect to postres without any problem (the connection url HASURA_GRAPHQL_DATABASE_URL seems to be fine). However, I cannot access my hasura instance from my job's container, in the script section. The output of the command is
wget: bad address 'graphql-engine'
I suppose that the job's container is not located in the same network as the service containers. How can I communicate with the graphql-engine service from my job container? I am currently using gitlab-runner 13.2.4.
EDIT
Looking at the amount of answers to this question, I guess there is no easy way. Therefore I'll switch to docker-compose. Instead of using the services that I can theoretically define in my job, I'll use docker-compose in my job and that'll achieve exactly the same purpose.

Related

How to setup Docker in Docker (DinD) on CloudBuild?

I am trying to run a script (unitest) that uses docker behind the scenes on a CI. The script works as expected on droneci but switching to CloudBuild it is not clear how to setup DinD.
For the droneci I basically use the DinD as shown here my question is, how do I translate the code to Google CloudBuild. Is it even possible?
I searched the internet for the syntax of CloudBuild wrt DinD and couldn't find something.
Cloud Build lets you create Docker container images from your source code. The Cloud SDK provides the container buildsubcommand for using this service easily.
For example, here is a simple command to build a Docker image:
gcloud builds submit -t gcr.io/my-project/my-image
This command sends the files in the current directory to Google Cloud Storage, then on one of the Cloud Build VMs, fetch the source code, run Docker build, and upload the image to Container Registry
By default, Cloud Build runs docker build command for building the image. You can also customize the build pipeline by having custom build steps.If you can use any arbitrary Docker image as the build step, and the source code is available, then you can run unit tests as a build step. By doing so, you always run the test with the same Docker image. There is a demonstration repository at cloudbuild-test-runner-example. This tutorial uses the demonstration repository as part of its instructions.
I would also recommend you to have a look at these informative links with similar use case:
Running Integration test on Google cloud build
Google cloud build pipeline
I managed to figure out a way to run Docker-in-Docker (DinD) in CloudBuild. To do that we need to launch a service in the background with docker-compose. Your docker-compose.yml script should look something like this.
version: '3'
services:
dind-service:
image: docker:<dnd-version>-dind
privileged: true
ports:
- "127.0.0.1:2375:2375"
- "127.0.0.1:2376:2376"
networks:
default:
external:
name: cloudbuild
In my case, I had no problem using versions 18.03 or 18.09, later versions should also work. Secondly, it is important to attach the container to the cloudbuild network. This way the dind container will be on the same network as every container spawned during your step.
To start the service you need to add a step to your cloudbuild.yml file.
- id: start-dind
name: docker/compose
args: ['-f', 'docker-compose.yml', 'up', '-d', 'dind-service']
To validate that the dind service works as expected, you can just create a ping step.
- id: 'Check service is listening'
name: gcr.io/cloud-builders/curl
args: ["dind-service:2375"]
waitFor: [start-dind]
Now if it works you can run your script as normal with dind in the background. What is important is to pass the DOCKER_HOST env variable so that the docker client can locate the docker engine.
- id: my-script
name: my-image
script: myscript
env:
- 'DOCKER_HOST=tcp://dind-service:2375'
Take note, any container spawned by your script will be located in dind-service, thus if you are to do any request to it you shouldn't do it to http://localhost but instead to the http://dind-service. Moreover, if you are to use private images you will require some type of authentication before running your script. For that, you should run gcloud auth configure-docker --quiet before running your script. Make sure your docker image has gcloud installed. This creates the required authentication credentials to run your app. The credentials are saved in path relevant to the $HOME variable, so make sure your app is able to access it. You might have some problems if you use tox for example.

Access a Docker Container via Alias behind a reverse proxy from another container

I'm trying to use ZAP-Docker as a yml file service, inside a Docker Container where Cypress is contained (End-to-End test execution).
I'm using ZAP as a reverse proxy, the idea would be to divert traffic from Cypress to ZAP. As you can imagine, however, I can't use the ip address of the zap container. So I'm trying a solution using aliases.
This is a piece of the yml file:
services:
- name: owasp/zap2docker-stable:latest
alias: zap
ports:
- "8092:8092"
entrypoint: ["zap.sh"]
command: ["-daemon", "-port", "8092", "-host", "0.0.0.0", "-config", "api.disablekey=true", "-config", "api.addrs.addr.name=.*", "-config", "api.addrs.addr.regex=true"]
This is the command I use to divert traffic to the ZAP container (using its alias)
HTTP_PROXY=http://zap:8092 npx cypress run
Cypress documentation Here
Unfortunately I get this error from Cypress by executing the test:
Cypress could not verify that this server is running:
> https://app.cloud
We are verifying this server because it has been configured as your `baseUrl`.
Cypress automatically waits until your server is accessible before running tests.

docker-compose execute command in sibling container

I am building an end to end test suite around a number of services. Some of these services aren't really services. They are actually procedural scripts which are run in sequence. These are executed at the command line and accept arguments, as you would expect a script to do.
We have docker images for these scripts/apps. I have compiled them into a docker-compose file. They are defined there as services which are sibling to the end to end test suite itself. So, for example:
docker-compose.yml
version: '3.4'
services:
script:
build: https://${GITHUB_ACCESS}:#github.com/company/script.git
image: script:e2e
e2e_tests:
build: .
image: e2e:e2e
Now, the e2e service needs to execute the script. Since the script isn't a service, I can't make a simple api call. How would I pass a command into the script container in order to execute it, from the e2e_tests container?
Problem
You want to call a command (let's say echo 1) which is located inside your script container (S1, derived from the image script:e2e) from your testing container (T1, derived from the image e2e_tests:e2e)
Solution
You could use the possibility to expose the Docker socket to a container.
Expose the Docker socket to container T1 (which should run the tests):
docker run -it --name T1 --volume /var/run/docker.sock:/var/run/docker.sock e2e_tests:e2e
Now from within the container T1 you are able to start other containers. This can be used to also start the script container S1 and execute a command:
docker run --name S1 scipt:e2e echo 1
1
The output of the command (here echo 1) will be piped to T1, so you can directly parse/use it.
How to transfer this to docker-compose.yml?
version: '3.4'
services:
script:
build: https://${GITHUB_ACCESS}:#github.com/company/script.git
image: script:e2e
e2e_tests:
build: .
image: e2e:e2e
volumes: /var/run/docker.sock:/var/run/docker.sock
Where to put the actual execution of the test (which in turn will have to execute docker run ... echo 1) depends on your specific usecase. You could:
execute this directly from within the CMD of e2e
put it into a script, which is executed by CMD of e2e
specify the entrypoint using the docker-compose.yml for e2e
Security
Be aware of the fact that the docker socket is highly privileged (it is like root). So exposing this socket might introduce security implications. It is on the same level as executing your tests on a system with password-less sudo, the tests won't get executed with privileged permissions, but an attacker which is able to modify your tests, could use it to gain privileged access. This might be ok, depending on your threat model.
For understanding the threat, see:
stackoverflow.com - Access Docker socket within container
Don't expose the Docker socket (not even to a container)
docker.com - Docker daemon attack surface

Passing environmental variables when deploying docker to remote host

I am having some trouble with my docker containers and environment variables.
Currently i have a docker-compose.yml with the following defined:
version: '2.1'
services:
some-service:
build:
context: .
image: image/replacedvalues
ports:
- 8080
environment:
- PROFILE=acc
- ENVA
- ENVB
- TZ=Europe/Berlin
some-service-acc:
extends:
service: some-service
environment:
- SERVICE_NAME=some-service-acc
Now when i deploy this manually (via SSH command line directly) on server A, it will take the environmental variables from Server A and put them in my container. So i have the values of ENVA and ENVB from the host in my container. Using the following command (after building the image ofcourse): docker-compose up some-service-acc.
We are currently developing a better infrastructure and want to deploy services via Jenkins. Jenkins is up and running in a docker container on server B.
I can deploy the service via Jenkins (Job-DSL, setting DOCKER_HOST="tcp://serverA:2375"temporary). So it will run all docker (compose) commands on ServerA from the Jenkins Container on Server B. The service is up and running except that it doesn't have values for the ENVA and the ENVB.
Jenkins runs the following with the Job-DSL groovy script:
withEnv(["DOCKER_HOST=tcp://serverA:2375"]) {
sh "docker-compose pull some-service-acc"
sh "docker-compose -p some-service-acc up -d some-service-acc"
}
I tried setting them in my Jenkins container and on Server B itself but neither worked. Only when i deploy manually directly on Server A it works.
When i use docker inspect to inspect the running container, i get the following output for the env block:
"Env": [
"PROFILE=acc",
"affinity:container==JADFG09gtq340iggIN0jg53ij0gokngfs",
"TZ=Europe/Berlin",
"SERVICE_NAME=some-service-acc",
"ENVA",
"ENVB",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"LANG=C.UTF-8",
"JAVA_VERSION=8",
"JAVA_UPDATE=121",
"JAVA_BUILD=13",
"JAVA_PATH=e9e7ea248e2c4826b92b3f075a80e441",
"JAVA_HOME=/usr/lib/jvm/default-jvm",
"JAVA_OPTS="
]
Where do i need to set the Environmental variables so that they will be passed to the container? I prefer to store the variables on Server A. But if this is not possible, can someone explain me how it could be done? It is not an option to hardcode the values in the compose file or anywhere else in the source as they contain sensitive data.
If i am asking this in the wrong place, please redirect me to where i should be.
Thanks!
You need to set the environment variables in the shell that is running the docker-compose command line. In Jenkins, that's best done be inside your groovy script (Jenkins doesn't use the host environment within the build slave):
withEnv(["DOCKER_HOST=tcp://serverA:2375", "ENVA=hello", "ENVB=world"]) {
sh "docker-compose pull some-service-acc"
sh "docker-compose -p some-service-acc up -d some-service-acc"
}
Edit: from the comments, you also want to pass secrets.
To do that, there are plugins like the Mask Password that would allow you to pass variables without them showing up in the logs or job configuration. (I'm fairly certain a determined intruder could still get to the values since Jenkins itself knows it and passes it to your script in clear text.)
The better option IMO is to use a secrets management tool inside of docker. Hashicorp has their Vault product which implements an encrypted K/V store where values are accessed with a time limited token and offers the ability to generate new passwords per request with integration into the target system. I'd consider this the highest level of security when fully configured, but you can configure this countless ways to suit your own needs. You'll need to write something to pull the secret and inject it into your container's environment (it's a rest protocol that you can add to your entrypoint).
The latest option from Docker itself is secrets management that requires the new Swarm Mode. You save your secret in the swarm and add it to the containers you want as a file using an entry in the docker-compose.yml version 3 format. If you already use Swarm Mode and can start your containers with docker stack deploy instead of docker-compose, this is a fairly easy solution to implement.

Docker port binding using gitlab-ci with gitlab-runner

I've noticed a problem, when configuring my gitlab-ci and gitlab-runner.
I want to have few separate application environments on one server, running on other external ports, but using same docker image.
What I want to achieve
deploy-dev running Apache at port 80 in container, but at external port 81
deploy-rcrunning Apache at port 80 in container, but on external port 82
I've seen that docker run has --publish argument, that allows port binding, like 80:81, but unfortunately I can't find any option in gitlab-ci.yml or gitlab-runner's config.toml to set that argument.
Is there any way to achieve port binding in Docker ran by gitlab-runner?
My gitlab-ci.yml:
before_script:
# Install dependencies
- bash ci/docker_install.sh > /dev/null
deploy:
image: webdevops/php-apache:centos-7-php56
stage: deploy
only:
- dockertest
script:
- composer self-update
- export SYMFONY_ENV=dev
- composer install
- app/console doc:sch:up --force
- app/console doc:fix:load -e=dev -n
- app/console ass:install
- app/console ass:dump -e=dev
tags:
- php
You're confusing two concepts: Continuous integration tasks, and docker deployment.
What you have configured is a continuous integration task. The idea is that these perform build steps and complete. Gitlab-ci will record the results of each build step and present it back to you. These can be docker jobs themselves, though they don't have to be.
What you want to do is deploy to docker. That is to say you want to start a docker job that contains your program. Going through this is probably beyond the scope of a stack overflow answer, but I'll try my best to outline what you need to do.
First take what you have a script already, and turn this into a dockerfile. Your dockerfile will need to add all the code in your repo, and then perform the composer / console scripts you list. Use docker build to turn this dockerfile into a docker image.
Next (optionally) you can upload the the docker image to a repository.
The final step is to perform a docker run command that loads up your image and runs it.
This sounds complicated, but it's really not. I have a ci pipeline that does this. One step runs: docker build ... forllowed by docker push ... and the next step runs docker run ... to spawn the new container.

Resources