如何在没有from标志的情况下编写多阶段Dockerfile

时间:2018-12-10 13:56:48

标签: docker jenkins jenkins-pipeline dockerfile

这实际上是我今天问的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 Server引擎的版本低于17.05,并且不会进行更新(也许稍后但现在不进行更新)。

我对Docker和Jenkins的知识非常陌生。如果有人可以帮助我以不带--from标志的方式使用Dockerfile,我将不胜感激。

更新:

上面提到的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

我在做什么错了?

3 个答案:

答案 0 :(得分:3)

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复制上一阶段提取的工件。

答案 1 :(得分:1)

@Carlos的答案是完全正确的。但是,当您使用詹金斯和管道时,您可能会对以下替代解决方案感到满意:

如果您在kubernetes发行版上使用具有动态pod预置的jenkins,则可以执行以下操作:

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

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

Jenkins文件看起来类似于:

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")
        }
    }
}

此Jenkins文件远非完整,仅说明了其工作方式。 在此处找到更多文档:

Jenkins kubernetes plugin

Jenkins docker global variable in scripted pipeline

答案 2 :(得分:0)

这是我最终的解决方案。

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}"
                    }

                }               
            }
        }
    }
}