首页 > 解决方案 > Rails 容器无法使用 gitlab ci 连接到 mysql 容器

问题描述

我正在为具有构建、测试和发布阶段的 Rails 应用程序设置一个简单的 gitlab ci:

build:
  stage: build
  script:
    - docker build --pull -t $TEST_IMAGE .
    - docker push $TEST_IMAGE

test:
  stage: test
  services:
    - docker:dind
  script:
    - docker pull $TEST_IMAGE
    - docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=mysql_strong_password mysql:5.7
    - docker run -e RAILS_ENV=test --link mysql:db $TEST_IMAGE bundle exec rake db:setup

build成功构建 docker 镜像并推送到注册表

test启动另一个我用作主机数据库的 mysql 容器,但在建立与 mysql 的连接时失败。

Couldn't create database for {"host"=>"db", "adapter"=>"mysql2", "pool"=>5, "username"=>"root", "encoding"=>"utf8", "timeout"=>5000, "password"=>"mysql_strong_password", "database"=>"my_tests"}, {:charset=>"utf8"}
(If you set the charset manually, make sure you have a matching collation)
rails aborted!
Mysql2::Error: Can't connect to MySQL server on 'db' (111 "Connection refused") 

--network我还尝试使用而不是方法创建单独的 docker 网络link,但没有帮助。

这只发生在 Gitlab runner 实例上。当我在本地机器上执行这些步骤时,它工作正常。

经过大量阅读,我认为这是 docker executor 的错误。我错过了什么吗?

标签: ruby-on-railsdockergitlab-ci

解决方案


拒绝连接表示容器知道如何相互连接,但目标容器在所选端口上没有任何接受连接。这很可能意味着您在数据库完成初始化之前启动应用程序。我的建议是更新/创建您的应用程序或在您的应用程序容器中创建一个入口点,轮询数据库以使其启动并运行,如果它没有启动几分钟后失败。我还建议使用网络而不是链接,因为不推荐使用链接并且不会优雅地处理重新创建的容器。

您看到的行为记录在 mysql 映像中

在 MySQL 初始化完成之前没有连接

如果容器启动时没有初始化数据库,则会创建一个默认数据库。虽然这是预期的行为,但这意味着在初始化完成之前它不会接受传入的连接。在使用同时启动多个容器的自动化工具(例如 docker-compose)时,这可能会导致问题。

如果您尝试连接到 MySQL 的应用程序没有处理 MySQL 停机时间或等待 MySQL 正常启动,那么在服务启动之前放置一个连接重试循环可能是必要的。有关官方图像中此类实现的示例,请参阅 WordPress 或 Bonita。

从链接的 wordpress 示例中,您可以看到他们的重试代码:

$maxTries = 10;
do {
    $mysql = new mysqli($host, $user, $pass, '', $port, $socket);
    if ($mysql->connect_error) {
        fwrite($stderr, "\n" . 'MySQL Connection Error: (' . $mysql->connect_errno . ') ' . $mysql->connect_error . "\n");
        --$maxTries;
        if ($maxTries <= 0) {
            exit(1);
        }
        sleep(3);
    }
} while ($mysql->connect_error);

在不更改应用程序本身的情况下等待 mysql 的示例入口点脚本可能如下所示:

#!/bin/sh
wait-for-it.sh mysql:3306 -t 300
exec "$@"

来自wait-for-it.shvishnubob /wait-for-itexec "$@"最后将 pid 1 替换为您传递的命令(例如bundle exec rake db:setup)。这种方法的缺点是数据库可能会在它真正准备好接受连接之前监听端口,所以我仍然建议在重试循环中使用您的应用程序进行完全登录。


推荐阅读