【问题标题】:How to write a multi-stage Dockerfile without from flag如何在没有 from 标志的情况下编写多阶段 Dockerfile
【发布时间】:2018-12-10 13:56:47
【问题描述】:

这其实是我今天问的this question的延续。

我有一个使用 --from 标志的多阶段 Dockerfile:

FROM docker.m.our-intra.net/microsoft/dotnet:2.1-sdk
WORKDIR /app
COPY . ./aspnetapp/
WORKDIR /app/aspnetapp
RUN dotnet publish -c Release -o out

FROM docker.m.our-intra.net/microsoft/dotnet:2.1.4-aspnetcore-runtime
WORKDIR /app
COPY --from=docker.m.our-intra.net/microsoft/dotnet:2.1-sdk /app/aspnetapp/MyProject.WebApi/out ./
ENTRYPOINT ["dotnet", "MyProject.WebApi.dll"]

借助这个文件,我可以在本地成功构建镜像。

但是我不能在我的 Jenkins 管道中使用这个 Dockerfile,因为 Jenkins 服务器引擎的版本低于 17.05,并且不会更新(可能稍后但不是现在)。

我是 Docker 和 Jenkins 方面的新手。如果有人可以帮助我修改 Dockerfile 以使我可以在没有 --from 标志的情况下使用它,我将不胜感激。

更新:

上面提到的Dockerfile是错误的。 Dockerfile 的工作版本在我的本地机器上成功构建映像并成功运行应用程序的帮助如下:

FROM docker.m.our-intra.net/microsoft/dotnet:2.1-sdk AS build
WORKDIR /app
COPY . ./aspnetapp/
WORKDIR /app/aspnetapp
RUN dotnet publish -c Release -o out

FROM docker.m.our-intra.net/microsoft/dotnet:2.1.4-aspnetcore-runtime AS runtime
WORKDIR /app
COPY --from=build /app/aspnetapp/MyProject.WebApi/out ./
ENTRYPOINT ["dotnet", "MyProject.WebApi.dll"]

更新 2:

我正在尝试听从 Carlos 的建议,现在我有两个 docker 文件。

这是我的Docker-build

FROM docker.m.our-intra.net/microsoft/dotnet:2.1-sdk
WORKDIR /app
COPY . ./aspnetapp/
WORKDIR /app/aspnetapp
RUN dotnet publish -c Release -o out

这是我的Dockerfile

FROM docker.m.our-intra.net/microsoft/dotnet:2.1.4-aspnetcore-runtime
COPY . .
ENTRYPOINT ["dotnet", "MyProject.WebApi.dll"]

这是我的Jenkinsfile

def docker_repository_url = 'docker.m.our-intra.net'
def artifact_group = 'some-artifact-group'                  
def artifact_name = 'my-service-api'

pipeline {
    agent {
        label 'build'
    }
    stages {
        stage('Checkout') {
            steps {
                script {
                    echo 'Checkout...'
                    checkout scm
                    echo 'Checkout Completed'
                }
            }
        }
        stage('Build') {
            steps {
                script {
                    echo 'Build...'
                    sh 'docker version'
                    sh 'docker build -t fact:v${BUILD_NUMBER} -f Dockerfile-build .'
                    echo 'Build Completed'
                }               
            }
        }
        stage('Extract artifact') {
            steps {
                script {
                    echo 'Extract...'
                    sh 'docker create --name build-stage-container fact:v${BUILD_NUMBER}'
                    sh 'docker cp build-stage-container:/app/aspnetapp/MyProject.WebApi/out .'
                    sh 'docker rm -f build-stage-container'
                    echo 'Extract Completed'
                }               
            }
        }
        stage('Copy compiled artifact') {
            steps {
                script {
                    echo 'Copy artifact...'
                    sh "docker build -t ${docker_repository_url}/${artifact_group}/${artifact_name}:v${BUILD_NUMBER} -f Dockerfile ."
                    echo 'Copy artifact Completed'
                }               
            }
        }
        stage('Push image') {
            steps {
                script {                                                    
                    withCredentials([[
                        $class: 'UsernamePasswordMultiBinding', 
                        credentialsId: 'jenkins',
                        usernameVariable: 'USERNAME', 
                        passwordVariable: 'PASSWORD'
                    ]]) {
                            def username = env.USERNAME
                            def password = env.PASSWORD

                            echo 'Login...'
                            sh "docker login ${docker_repository_url} -u ${username} -p ${password}"
                            echo 'Login Successful' 

                            echo 'Push image...'
                            sh "docker push ${docker_repository_url}/${artifact_group}/${artifact_name}:v${BUILD_NUMBER}"   
                            echo 'Push image Completed'
                    }                               
                }               
            }
        }
    }
}

所有步骤都成功,但是当我尝试在本地运行映像(从 Maven 拉取它之后)或在 OpehShift 集群上运行它时失败并显示:

您的意思是运行 dotnet SDK 命令吗?请从以下位置安装 dotnet SDK: http://go.microsoft.com/fwlink/?LinkID=798306&clcid=0x409

我做错了什么?

【问题讨论】:

    标签: docker jenkins jenkins-pipeline dockerfile


    【解决方案1】:

    TL;DR:您需要自己复制底层功能,在 Docker 之外

    首先,您使用了错误的--from 选项。要从之前的构建阶段复制,you must refer to its index or its name,例如:

    FROM docker.m.our-intra.net/microsoft/dotnet:2.1-sdk
    ...
    
    FROM docker.m.our-intra.net/microsoft/dotnet:2.1.4-aspnetcore-runtime
    COPY --from=0 /app/aspnetapp/MyProject.WebApi/out ./
    

    FROM docker.m.our-intra.net/microsoft/dotnet:2.1-sdk AS build-stage
    ...
    FROM docker.m.our-intra.net/microsoft/dotnet:2.1.4-aspnetcore-runtime
    COPY --from=build-stage /app/aspnetapp/MyProject.WebApi/out ./
    

    使用您当前的 Dockerfile,它会尝试从上游 docker 映像复制文件,而不是从之前的构建阶段。

    其次,您不能使用 17.05 之前的版本进行多阶段 Docker 构建。您需要自己在 Docker 之外复制底层功能。

    为此,您可以使用一个 Dockerfile 来构建您的工件并基于该映像运行一个一次性容器,从中提取工件。你不需要运行容器,你可以简单地create it with docker create(这会创建可写容器层):

    docker create --name build-stage-container build-stage-image
    docker cp build-stage-container:/app/aspnetapp/MyProject.WebApi/out .
    

    然后,您可以使用第二个 Dockerfile 来构建镜像,复制从前一阶段提取的工件,并使用来自构建上下文的简单 COPY

    【讨论】:

    • 欢迎来到 SO 和很好的答案!我将在另一个答案中概述一个詹金斯特定的解决方案作为替代方案。
    • @DmitryS 当您根据推送到${docker_repository_url} 的最终映像运行容器时,您会收到该错误,对吧?如果这是你的容器在运行时的输出,那是dotnet MyProject.WebApi.dll(入口点)告诉你的。您可能在运行时使用了错误的图像,缺少某些库/SKD,或者将错误的命令作为入口点运行
    • 但是我使用相同的基础镜像和相同的入口点,就像我在一个 Docker 文件的情况下使用的一样。我可以使用一个 Dockerfile 在本地机器上成功构建映像并运行该应用程序。当我使用 Jenkins 和两个 dockerfile 执行此操作时,我得到了最终图像(与一个 dockerfile 的大小相同)但可以运行它...
    • 当您在本地使用多阶段 Dockerfile 时,您将工作目录设置为 /app,但是当您使用两个单独的 Dockerfile 时,您不会这样做。您的工件是否取决于其位置?
    • 谢谢你,卡洛斯。我终于找到了可行的解决方案。我也会接受你的回答,我会发布我的 dockerfiles 和 jenkinsfile 的最终版本。
    【解决方案2】:

    @Carlos 的回答是完全有效的。但是,当您使用 jenkins 和管道时,您可能会对以下替代解决方案感到满意:

    如果您在 kubernetes-distribution 上使用带有动态 pod-provisioning 的 jenkins,您可以执行以下操作:

    • 为您的构建使用基于<registry>/microsoft/dotnet:2.1-sdk 的 pod 模板。以常规 dotnet 方式在该 pod 中编译您的应用程序。
    • 保留 Dockerfile 的第二部分,但只需将编译后的工件复制到 docker-image 中。

    总之,您将 Dockerfile 的第一部分移到 Jenkinsfile 中以进行应用程序构建。第二部分仍然是从已经编译的二进制文件中进行 docker-build。

    Jenkinsfile 看起来类似于:

    podTemplate(
        ...,
        containers: ['microsoft/dotnet:2.1-sdk', 'docker:1.13.1'],
        ...
    ) {
        container('microsoft/dotnet:2.1-sdk') {
            stage("Compile Code") {
                sh "dotnet restore"
                sh "dotnet publish -c Release -o out"
            }
        }
        container('docker:1.13.1') {
            stage("Build Docker image") {
                docker.build("mydockerimage:1.0")
            }
        }
    }
    

    此 Jenkinsfile 远未完成,仅说明了它的工作原理。 在此处查找更多文档:

    Jenkins kubernetes plugin

    Jenkins docker global variable in scripted pipeline

    【讨论】:

      【解决方案3】:

      这是我最后的工作解决方案。

      Docker-build:

      FROM docker.m.our-intra.net/microsoft/dotnet:2.1-sdk
      WORKDIR /app
      COPY . ./aspnetapp/
      WORKDIR /app/aspnetapp
      RUN dotnet publish -c Release -o out
      

      Dockerfile:

      FROM docker.m.our-intra.net/microsoft/dotnet:2.1.4-aspnetcore-runtime
      ADD output/out /output
      WORKDIR /output
      ENTRYPOINT ["dotnet", "MyProject.WebApi.dll"]
      

      Jenkinsfile:

      def docker_repository_url = 'docker.m.our-intra.net'
      def artifact_group = 'some-artifact-group'                  
      def artifact_name = 'my-service-api'
      
      pipeline {
          agent {
              label 'build'
          }
          stages {
              stage('Checkout') {
                  steps {
                      script {
                          echo 'Checkout...'
                          checkout scm
                          echo 'Checkout Completed'
                      }
                  }
              }
              stage('Build') {
                  steps {
                      script {
                          echo 'Build...'
                          sh 'docker version'
                          sh "docker build -t sometag:v${BUILD_NUMBER} -f Dockerfile-build ."
                          echo 'Build Completed'
                      }               
                  }
              }
              stage('Extract artifact') {
                  steps {
                      script {
                          echo 'Extract...'
                          sh "docker run -d --name build-stage-container sometag:v${BUILD_NUMBER}"
                          sh 'mkdir output'
                          sh 'docker cp build-stage-container:/app/aspnetapp/MyProject.WebApi/out output'
                          sh 'docker rm -f build-stage-container'
                          sh "docker rmi -f sometag:v${BUILD_NUMBER}"
                          echo 'Extract Completed'
                      }               
                  }
              }
              stage('Copy compiled artifact') {
                  steps {
                      script {
                          echo 'Copy artifact...'
                          sh "docker build -t ${docker_repository_url}/${artifact_group}/${artifact_name}:v${BUILD_NUMBER} -f Dockerfile ."
                          echo 'Copy artifact Completed'
                      }               
                  }
              }
              stage('Push image') {
                  steps {
                      script {                                                    
                          withCredentials([[
                              $class: 'UsernamePasswordMultiBinding', 
                              credentialsId: 'jenkins',
                              usernameVariable: 'USERNAME', 
                              passwordVariable: 'PASSWORD'
                          ]]) {
                                  def username = env.USERNAME
                                  def password = env.PASSWORD
      
                                  echo 'Login...'
                                  sh "docker login ${docker_repository_url} -u ${username} -p ${password}"
                                  echo 'Login Successful' 
      
                                  echo 'Push image...'
                                  sh "docker push ${docker_repository_url}/${artifact_group}/${artifact_name}:v${BUILD_NUMBER}"   
                                  echo 'Push image Completed'
                                  sh "docker rmi -f ${docker_repository_url}/${artifact_group}/${artifact_name}:v${BUILD_NUMBER}"
                          }
      
                      }               
                  }
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多