Gitlab CI runner无法暴露嵌套Docker容器的端口

27
当使用GitLab CI以及gitlab-ci-multi-runner时,我无法使内部启动的Docker容器将其端口暴露给“主机”,即运行构建的Docker镜像。
我的.gitlab-ci.yml文件:
test:
  image: docker
  stage: test
  services:
    - docker:dind
  script:
    - APP_CONTAINER_ID=`docker run -d --privileged -p "9143:9143" appropriate/nc nc -l 9143`
    - netstat -a
    - docker exec $APP_CONTAINER_ID netstat -a
    - nc -v localhost 9143

我的命令:

gitlab-ci-multi-runner exec docker --docker-privileged test

输出结果:
$ netstat -a
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 runner--project-1-concurrent-0:54664 docker:2375             TIME_WAIT
tcp        0      0 runner--project-1-concurrent-0:54666 docker:2375             TIME_WAIT
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags       Type       State         I-Node Path

$ docker exec $APP_CONTAINER_ID netstat -a
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 0.0.0.0:9143            0.0.0.0:*               LISTEN
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags       Type       State         I-Node Path

$ nc -v localhost 9143
ERROR: Build failed: exit code 1
FATAL: exit code 1

我在这里做错了什么?

以下是原始问题 - 上面是一个更短、更易于测试的示例

我有一个应用程序镜像,监听端口9143。它的启动和配置是通过docker-compose.yml管理的,在我的本地机器上使用docker-compose up非常好用 - 我可以轻松访问localhost:9143

然而,在GitLab CI(gitlab.com版本)上运行时,该端口似乎没有被暴露。

我的.gitlab-ci.yml的相关部分:

test:
  image: craigotis/buildtools:v1
  stage: test
  script:
    - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.gitlab.com/craigotis/myapp
    - docker-compose up -d
    - sleep 60 # a temporary hack to get the logs
    - docker-compose logs
    - docker-machine env
    - docker-compose port app 9143
    - netstat -a
    - docker-compose ps
    - /usr/local/bin/wait-for-it.sh -h localhost -p 9143 -t 60
    - cd mocha
    - npm i
    - npm test
    - docker-compose down

输出结果为:
$ docker-compose logs
...
app_1  | [Thread-1] INFO spark.webserver.SparkServer - == Spark has ignited ...
app_1  | [Thread-1] INFO spark.webserver.SparkServer - >> Listening on 0.0.0.0:9143
app_1  | [Thread-1] INFO org.eclipse.jetty.server.Server - jetty-9.0.z-SNAPSHOT
app_1  | [Thread-1] INFO org.eclipse.jetty.server.ServerConnector - Started ServerConnector@6919dc5{HTTP/1.1}{0.0.0.0:9143}
...

$ docker-compose port app 9143
0.0.0.0:9143

$ netstat -a
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       
tcp        0      0 runner-e11ae361-project-1925166-concurrent-0:53646 docker:2375             TIME_WAIT   
tcp        0      0 runner-e11ae361-project-1925166-concurrent-0:53644 docker:2375             TIME_WAIT   
tcp        0      0 runner-e11ae361-project-1925166-concurrent-0:53642 docker:2375             TIME_WAIT   
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags       Type       State         I-Node Path

$ docker-compose ps
stty: standard input: Not a tty
    Name                  Command               State                Ports               
----------------------------------------------------------------------------------------
my_app_1   wait-for-it.sh mysql_serve ...   Up      8080/tcp, 0.0.0.0:9143->9143/tcp 
mysql_server   docker-entrypoint.sh --cha ...   Up      3306/tcp     

$ /usr/local/bin/wait-for-it.sh -h localhost -p 9143 -t 60
wait-for-it.sh: waiting 60 seconds for localhost:9143
wait-for-it.sh: timeout occurred after waiting 60 seconds for localhost:9143

我的 docker-compose.yml 文件内容如下:

version: '2'

networks:
    app_net:
        driver: bridge

services:
    app:
        image: registry.gitlab.com/craigotis/myapp:latest
        depends_on:
        - "db"
        networks:
        - app_net
        command: wait-for-it.sh mysql_server:3306 -t 60 -- java -jar /opt/app*.jar
        ports:
        - "9143:9143"

    db:
        image: mysql:latest
        networks:
        - app_net
        container_name: mysql_server
        environment:
        - MYSQL_ALLOW_EMPTY_PASSWORD=true

看起来我的应用程序容器正在监听 9143,并且已经正确地暴露给共享的 GitLab runner,但似乎实际上并没有暴露出来。它在我的本地机器上运行良好 - 在运行在 GitLab 上的 Docker 容器中工作需要做一些特殊的解决方法/调整吗?


在您的本地计算机上,它运行在哪个主机地址上?您的本地计算机操作系统是什么? - Rohit Dhiman
你最终找到答案了吗? - mslot
我已经制作了一个最小化的工作示例,供他人使用:https://gitlab.com/mslot/dind.sandbox - mslot
5个回答

18
当使用docker:dind时,会创建一个容器并在其中设置你的docker-compose容器。它将端口暴露给docker:dind容器中的本地主机。无法从执行代码的环境中使用localhost进行访问。 docker的主机名已经为你设置好,以便引用此docker:dind容器。可以使用cat /etc/hosts进行检查。
不要使用localhost:9143进行引用,而应该使用docker:9143

1
我已经检查了/etc/hosts文件,确实只有特殊名称“docker”可用(没有“mysql”等)。 - admirabilis
如果我使用 docker run 启动一个内部容器,这个方法是否也适用?因为在 GitLab CI 内,对于 docker 的所有操作都失败了。 - Dmitriy Popov
我还检查了 cat /etc/hosts。它不包含 docker 名称。 - Dmitriy Popov

13

官方GitLab.com上的GitLab CI文档提到了PostgreSQL的示例

它的CI工作并不会尝试连接到localhost,而是连接到服务名称

services关键字定义另一个在构建期间运行并链接到image关键字定义的docker镜像的docker镜像。这样,您就可以在构建时间访问服务镜像。

MySQL的服务容器将在主机名mysql下可用。
因此,为了访问您的数据库服务,您必须连接到名为mysql的主机,而不是套接字或localhost

您可以检查是否适用于您的情况,并尝试在app:9143而不是localhost:9143中访问应用程序服务。


4
在我的.gitlab-ci.yml文件中,我实际上正在使用services来处理其他事情,比如docker:dind。然而,我希望在测试阶段使用docker-compose的原因是,在测试环境中,我不需要在docker-compose.yml.gitlab-ci.yml中重复编排MySQL、应用程序和其他相关镜像。我已经拥有一个工作的Compose文件,我已经花费了一些时间进行完善,并且在开发机器上运行正常(而.gitlab-ci.yml则无法使用),因此如果可能的话,我确实想要重复使用它。 - Craig Otis
不行,我似乎无论如何都无法让它正常工作。无论是作为服务运行,还是尝试通过docker或docker-compose启动它,我都无法连接到服务,只有在GitLab CI上运行时才能连接。在本地运行时它总是完美地工作。 - Craig Otis
@CraigOtis,这是否是您的主机和Linux虚拟机之间的端口转发问题?(如https://dev59.com/k5fga4cB1Zd3GeqPDPA4#37771161或https://dev59.com/cpTfa4cB1Zd3GeqPRXyX#35646587) - VonC
问题是,localhost已经是一个Docker容器了。.gitlab-ci.yml文件指定了一个Docker镜像,在其中运行我的所有命令。因此,我的应用镜像是两层深度的。我只想从第一级容器中访问它,这是GitLab CI为运行我的构建而创建的容器。 - Craig Otis
@CraigOtis 这个链接 https://gitlab.com/gitlab-org/gitlab-ce/issues/22707 有帮助吗? - VonC
显示剩余13条评论

2
如果您使用Docker执行器通过套接字绑定运行GitLab CI Runner,请使用“host.docker.internal”主机,因为您的应用程序正在主机上运行而不是在虚拟机上。
# Run and ssh into dind-container
docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock docker:20.10-dind /bin/sh

# Next commands run in dind-container

docker run --rm -d -p 8888:80 nginx:alpine
...

wget -qO- "http://localhost:8888/"
wget: can't connect to remote host (127.0.0.1): Connection refused

wget -qO- "http://127.0.0.1:8888/"
wget: can't connect to remote host (127.0.0.1): Connection refused

wget -qO- "http://docker:8888/"
wget: bad address 'docker:8888'

wget -qO- "http://host.docker.internal:8888/"
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...


1

你的 docker-compose.yml 文件看起来没问题。

但是我认为你的 IP 或端口路由有误。 从你分享的信息中,我可以看到你的应用在 IP 0.0.0.0 上运行在 9143 端口,即 0.0.0.0:9143

而你正在使用 localhost:9143 访问它, 这可能被解释为 127.0.0.1:9143

根据this

127.0.0.1 is the loopback address (also known as localhost).
0.0.0.0 is a non-routable meta-address used to designate an invalid, unknown, or non-applicable target (a ‘no particular address’ place holder).

请尝试在 127.0.0.1:9143 上运行您的应用程序,然后分享结果。

更新

或者您可以使用服务来运行它,如 文档 所建议:

services 关键字定义了另一个 Docker 镜像,该镜像在构建期间运行,并链接到 image 关键字定义的 Docker 镜像。这使您可以在构建时访问服务镜像。

MySQL 的服务容器将在主机名为 mysql 下可访问。 因此,为了访问您的数据库服务,您必须连接到名为 mysql 的主机,而不是套接字或 localhost


1
我认为你对0.0.0.0的理解是错误的,继续阅读你链接的文章,你会发现“在服务器的上下文中,0.0.0.0表示本地机器上的所有IPv4地址。” 在这种情况下,它只是意味着“监听所有接口”(绑定到127.0.0.1的服务器无法从机器外部访问)。 - Kjell Andreassen

0
通常情况下,Docker机器不会在本地主机上运行,而是在具有其他IP地址的Docker主机上运行。尝试使用docker-machine ip命令获取您的Docker主机IP地址。

问题并不在于我确定它运行的主机名 - 而是无论是通过使用Docker Compose或带有-p "...:..."的常规Docker运行在localhost上,还是当我将我的应用程序镜像配置为在GitLab配置中运行为service:时,当它实际上在GitLab CI上运行时,应用程序镜像是无法访问的 - 即使它在我的本地机器上正常工作,并且根据应用程序日志似乎启动良好(并保持运行)。 - Craig Otis
也许我没有理解你的意思(从未使用过GitLab CI),但我只是想说,由于任何docker客户端都可以配置为针对VM或任何其他远程docker主机工作(使用docker-machine env设置DOCKER_HOST),因此GitLab的docker客户端可能被配置为针对除localhost之外的主机运行,在这种情况下,容器被创建并运行在不同的主机上,具有不同的IP地址(并且不会在等待localhost上可用)。所以,我想知道你是否尝试过检查这一点。 - Yoav Aharoni
谢谢Yoav,我的问题已经更新,提供了一个更简单的例子。 - Craig Otis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接