在Jenkins中使用Docker容器链接

5

我正在尝试运行一个可以用于数据库迁移脚本的CI系统。这可以防止由于语法错误而无法在迁移时运行SQL脚本。对于我们的本地设置,我们使用docker-compose同时运行多个容器。但很遗憾,在Jenkins pipeline中这不是一个选项。

我创建了这个Jenkinsfile,并采用以下策略:

  1. 运行一个postgres容器,使用更多或更少的默认参数
  2. 将另一个postgres容器链接到第一个容器,仅使用pg_isready命令行等待DB准备好接收连接
  3. 使用flyway容器针对第1步中设置的DB运行数据库迁移。最终计划还要运行包括web应用程序的E2E测试

我的实现基于此处的文档(运行docker sidecar)。然而,这并没有起作用,第一个容器(步骤1)似乎停止了。我添加了一些额外的调试(try catch)以查看此容器的日志。

Jenkinsfile的内容如下:

def docker_repository = '<CUSTOM-REGISTRY>'
def docker_user_credentialsId = '<DOCKER-USER>'

pipeline {
   agent { label 'docker && linux && nonprod' }

options {
    buildDiscarder(logRotator(daysToKeepStr: '90', numToKeepStr: '20', artifactDaysToKeepStr: '90', artifactNumToKeepStr: '20'))
    timeout(time: 20, unit: 'MINUTES') 
}
stages {
    stage('build & test') {
        environment {
            POSTGRES_DB = 'mydb'
            POSTGRES_USER = 'postgres'
            POSTGRES_PASSWORD = 'postgres'                
            FLYWAY_URL = 'jdbc:postgresql://localhost:5432/mydb'
            // FLYWAY_URL = 'jdbc:postgresql://db-container:5432/mydb'
            FLYWAY_USER = 'postgres'
            FLYWAY_PASSWORD = 'postgres'
        }
        steps {
            checkout scm
            withCredentials([ usernamePassword(credentialsId: docker_user_credentialsId, passwordVariable: 'ARTIFACTORY_API_TOKEN', usernameVariable: 'ARTIFACTORY_API_USER'),]) {}                                
            script {
                docker.withRegistry(docker_repository, docker_user_credentialsId) {
                    docker.image('<REGISTRY>/postgres').withRun("--name=db-container -e POSTGRES_PASSWORD=postgres") { c ->
                        try {
                            docker.image('<REGISTRY>/postgres').inside("--link ${c.id}:db") {                                
                                sh '''
                                    while ! pg_isready -h db -p 5432
                                    do
                                         echo $
                                         echo "$(date) - waiting for database to start"
                                         sleep 10
                                    done
                                '''
                            }
                            docker.image('<REGISTRY>/flyway/flyway').inside("--link ${c.id}:db") {
                                sh 'info'
                                // sh 'migrate'
                            }
                        } catch (exc) {
                            sh "docker logs ${c.id}"
                            throw exc
                        }
                    }
                }
            }                
        }
    }
}
post {
    always {
        cleanWs()
    }
}

为了获取这些日志,步骤2被暂时删除了,因为步骤2在循环中等待,而docker logs命令不能打断程序。

+ docker logs 9d9e8699b57430e288520c485c8333a0261f9283f749aec2832cfb0e5f19ef9e
 The files belonging to this database system will be owned by user "postgres".
 This user must also own the server process.
 The database cluster will be initialized with locale "en_US.utf8".
 The default database encoding has accordingly been set to "UTF8".
 The default text search configuration will be set to "english".

 Data page checksums are disabled.
 fixing permissions on existing directory /var/lib/postgresql/data ... ok
  creating subdirectories ... ok
  selecting dynamic shared memory implementation ... posix
  selecting default max_connections ... 100
  selecting default shared_buffers ... 128MB 
  selecting default time zone ... Etc/UTC
  creating configuration files ... ok
  running bootstrap script ... ok
  performing post-bootstrap initialization ... ok
  syncing data to disk ... ok
  Success. You can now start the database server using:
  pg_ctl -D /var/lib/postgresql/data -l logfile start
  initdb: warning: enabling "trust" authentication for local connections
  You can change this by editing pg_hba.conf or using the option -A, or
   --auth-local and --auth-host, the next time you run initdb.
  waiting for server to start....2021-03-31 15:21:29.923 UTC [48] LOG:  starting PostgreSQL 13.2 
  (Debian 13.2-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit
  2021-03-31 15:21:29.929 UTC [48] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
  2021-03-31 15:21:29.946 UTC [49] LOG:  database system was shut down at 2021-03-31 15:21:29 UTC
  2021-03-31 15:21:29.951 UTC [48] LOG:  database system is ready to accept connections
  done

  server started

   /usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d/*
   2021-03-31 15:21:30.062 UTC [48] LOG:  received fast shutdown request
   waiting for server to shut down....2021-03-31 15:21:30.064 UTC [48] LOG:  aborting any active transactions

   2021-03-31 15:21:30.065 UTC [48] LOG:  background worker "logical replication launcher" (PID 55) 
     exited with exit code 1

    2021-03-31 15:21:30.071 UTC [50] LOG:  shutting down
    2021-03-31 15:21:30.099 UTC [48] LOG:  database system is shut down
    done

    server stopped
    PostgreSQL init process complete; ready for start up.

    2021-03-31 15:21:30.188 UTC [1] LOG:  starting PostgreSQL 13.2 (Debian 13.2-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit

    2021-03-31 15:21:30.190 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
    2021-03-31 15:21:30.190 UTC [1] LOG:  listening on IPv6 address "::", port 5432
    2021-03-31 15:21:30.196 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
    2021-03-31 15:21:30.203 UTC [67] LOG:  database system was shut down at 2021-03-31 15:21:30 UTC
    2021-03-31 15:21:30.208 UTC [1] LOG:  database system is ready to accept connections

这里的Postgres和Flyway版本不应该起到重要作用,但从日志中可以看出,Postgres版本为13.2,由默认标签提供。

有任何提示吗?

2个回答

0

经过一些尝试和错误,我们找到了解决方法。虽然它与最初的设置非常相似,但关键变化在于选择使用insidewithRun。最终的Jenkinsfile如下所示。

// credentials
def docker_user_credentialsId = '<DOCKER-USER>'
def docker_repository = '<CUSTOM-REGISTRY>'

// docker images
def pg = docker.image('postgres:12-alpine')
def flyway = docker.image('flyway/flyway:7-alpine')

pipeline {
    agent none

    options {
        buildDiscarder(logRotator(daysToKeepStr: '90', numToKeepStr: '20', artifactDaysToKeepStr: '90', artifactNumToKeepStr: '20'))
        timeout(time: 120, unit: 'MINUTES') 
    }

    stages {
        stage('db migration test') {
            agent { 
                label 'docker && linux'
            }

            environment {
                PGHOST = 'db'
                PGPORT = '5432'
                PGDATABASE = 'postgres'
                PGUSER = 'postgres'
                PGPASSWORD = 'postgres'
            }

            steps {
                script {
                    docker.withRegistry(docker_repository, docker_user_credentialsId) {
                        def db = pg.withRun(
                            "-v ${WORKSPACE}/docker/init-user-db.sh:/docker-entrypoint-initdb.d/init-user-db.sh -e POSTGRES_USER=${env.PGUSER} -e POSTGRES_PASSWORD=${env.PGPASSWORD} -e POSTGRES_DB=${env.PGDATABASE}"
                        ) { db ->
                            pg.inside("--link ${db.id}:${env.PGHOST}") {                                
                                sh '''
                                    echo "$(date) - waiting for database to start"
                                    while ! pg_isready
                                    do
                                        sleep 10
                                    done
                                '''
                            }
                            flyway.withRun("-e FLYWAY_LOCATIONS=filesystem:/tmp/database/server,filesystem:/tmp/database/local -e FLYWAY_URL=jdbc:postgresql://${PGHOST}:${env.PGPORT}/${env.PGDATABASE} -e FLYWAY_USER=${env.PGUSER} -e FLYWAY_PASSWORD=${env.PGPASSWORD} -v ${WORKSPACE}/database/local:/tmp/database/local -v ${WORKSPACE}/database/server:/tmp/database/server --link ${db.id}:db", "migrate") { f ->
                                sh "docker logs -f ${f.id}"
                                def output = sh script: "docker inspect ${f.id} --format='{{.State.ExitCode}}'", returnStdout: true
                                sh "exit ${output}"
                            }
                        }
                    }
                }
            }
        }
    }
 }

0

假设您正在使用官方的Postgres Docker镜像,可能的根本原因是容器的entrypoint脚本失败了。

来自图像的Docker页面:

警告:/ docker-entrypoint-initdb.d中的脚本仅在启动具有空数据目录的容器时运行;[...] 一个常见问题是,如果您的/docker-entrypoint-initdb.d脚本之一失败(这将导致入口点脚本退出),并且编排程序以已初始化的数据目录重新启动容器,则不会继续进行您的脚本

解决方案

假设入口点进程确实失败了(可能是因为它看到了一个未拥有并且由entrypoint设计覆盖它的DB,因为它链接到另一个postgres容器),您可以通过设置自己的entrypointcmd来克服这个问题,从而防止该图像尝试创建您不打算使用的数据库,即:

FROM postgres

#Switch back to default entrypoint
ENTRYPOINT ["/bin/sh"]

#Let the container start and do nothing until the pipeline kicks in
CMD ["-c","sleep infinity"]

基于上述Dockerfile创建一个新的镜像,将其推送,并在Jenkins的流水线中定义它(仅适用于sidecar)。这样,您就会拥有一个稳定的容器,与实际的db容器相连,流水线可以跳转到该容器。

最后,关于在sidecar中启动的命令,我建议使用localhost而不是db作为目标主机,在命令问题的pg_isready -h db -p 5432中,结果为:pg_isready -h localhost -p 5432


不幸的是,这也行不通。第一个postgres容器仍然在旁路车连接到它之前停止。 - Parthiva

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