使用多级容器进行C++开发

更新日期:2020年1月10日:修正了链接到文章来源,这是打破了重构回购。

null

容器是配置可复制构建环境的好工具。很容易找到提供各种C++环境的DOCKFrm文件。不幸的是,很难找到关于如何使用新技术(如多阶段构建)的指导。这篇文章将向您展示如何利用多级容器的能力来开发C++。这与C++开发人员无关,不管你使用什么工具。

多阶段构建是Dockerfiles,它使用多个FROM语句,其中每个语句开始构建的一个新阶段。您还可以命名构建阶段,并将早期阶段的输出复制到后期阶段。在此功能可用之前,通常会看到生成容器定义,其中输出被复制到主机,然后复制到部署容器中。这将相关容器的定义扩展到多个Dockerfiles上,这些Dockerfiles通常通过脚本一起驱动。多阶段构建比这种方法方便得多,而且不那么脆弱。要查看多阶段构建前后的完整示例,我建议查看 Docker官方多阶段构建文档 .

让我们来看一个C++应用程序的多阶段构建DOCKEFLE文件。这是一个应用程序,它公开一个服务来接收图像,使用OpenCV处理图像来圈出任何找到的面,并公开另一个端点来检索处理后的图像。下面是一个完整的多阶段Dockerfile,它生成一个编译应用程序的生成容器,然后是一个运行时容器,该容器接受该输出,并且只具有运行应用程序(而不是生成应用程序)所需的依赖项。 这是本文的来源 .

FROM alpine:latest as build

LABEL description="Build container - findfaces"

RUN apk update && apk add --no-cache  
    autoconf build-base binutils cmake curl file gcc g++ git libgcc libtool linux-headers make musl-dev ninja tar unzip wget

RUN cd /tmp 
    && wget https://github.com/Microsoft/CMake/releases/download/untagged-fb9b4dd1072bc49c0ba9/cmake-3.11.18033000-MSVC_2-Linux-x86_64.sh 
    && chmod +x cmake-3.11.18033000-MSVC_2-Linux-x86_64.sh 
    && ./cmake-3.11.18033000-MSVC_2-Linux-x86_64.sh --prefix=/usr/local --skip-license 
    && rm cmake-3.11.18033000-MSVC_2-Linux-x86_64.sh

RUN cd /tmp 
    && git clone https://github.com/Microsoft/vcpkg.git -n  
    && cd vcpkg 
    && git checkout 1d5e22919fcfeba3fe513248e73395c42ac18ae4 
    && ./bootstrap-vcpkg.sh -useSystemBinaries

COPY x64-linux-musl.cmake /tmp/vcpkg/triplets/

RUN VCPKG_FORCE_SYSTEM_BINARIES=1 ./tmp/vcpkg/vcpkg install boost-asio boost-filesystem fmt http-parser opencv restinio

COPY ./src /src
WORKDIR /src
RUN mkdir out 
    && cd out 
    && cmake .. -DCMAKE_TOOLCHAIN_FILE=/tmp/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-linux-musl 
    && make

FROM alpine:latest as runtime

LABEL description="Run container - findfaces"

RUN apk update && apk add --no-cache  
    libstdc++

RUN mkdir /usr/local/faces
COPY --from=build /src/haarcascade_frontalface_alt2.xml /usr/local/faces/haarcascade_frontalface_alt2.xml

COPY --from=build /src/out/findfaces /usr/local/faces/findfaces

WORKDIR /usr/local/faces

CMD ./findfaces

EXPOSE 8080

Dockerfile的第一部分描述了应用程序的构建环境。我们在FROM行中使用了AS关键字来标识构建的这个阶段,以便在后续阶段中引用。第一行调用包管理器,并下拉出编译器的build-essential包和我需要的库的dev包。我们正在拉vcpkg来获取我们的库依赖项。复制行将主机src文件夹复制到容器中。最后的RUN语句使用CMake构建应用程序。这个容器没有入口点,因为我们只需要运行一次并构建输出。

第二个FROM语句开始我们的多阶段构建的下一部分。在这里,我们的依赖性减少了,因为我们不需要编译器或开发包。因为我们静态链接了我们的库,所以我们甚至不需要它们,但是我们确实需要GCC libs,因为我们使用GCC构建,Alpine使用muslc。请注意使用–from=build的复制行。在这里,我们从构建容器复制内容,而不需要导出到主机并复制回运行时容器。CMD语句在容器中启动服务,EXPOSE语句记录容器用于服务的端口。

你可以继续建造这个容器。如果你这样做,你可以标记图像,但只有最后的图像被标记;多阶段构建的早期阶段的图像并非如此。如果您想使用这些早期图像作为其他容器的基础,您可以通过指定要停止的构建阶段的目标来标记。例如,要运行构建阶段,我可以运行以下命令停止并标记图像:

docker build --target build -t findfaces/build .

如果我想运行整个构建,我可以运行以下命令并从最后阶段标记图像。如果您已经运行了早期阶段,将使用它的图像缓存。

docker build -t findfaces/run .

现在使用我的运行时容器:

docker run -d --rm -p 8080:8080 --name findfaces findfaces/run

此命令基于findfaces/run映像运行容器,在容器启动时从容器中分离(-d),在容器停止时移除容器(-rm),暴露映射到容器上相同端口的端口8080(-p),并将容器命名为findfaces。

现在容器已启动,我可以使用curl访问服务:

curl -X PUT -T mypicture.jpg localhost:8080/files?submit=picture.jpg
curl -X GET localhost:8080/files/facespicture.jpg > facespicture.jpg

如果图像中有人脸,我们的OpenCV库可以识别它们在输出图像中被圈起来,输出图像中使用的名称前面加了“faces”。

完成应用程序后,我们可以停止它并删除容器:

docker stop findfaces

阿尔卑斯vs德比安

上面我们使用Alpine Linux作为我们的基础。这是一个非常小的Linux发行版,与Debian等更常见的发行版有一些不同。如果您检查Dockerfile,您会注意到我们将一个文件x64-linux-musl.cmake从src目录复制到vcpkgtriplets目录。这是因为Alpine使用muslc而不是GCC,所以我们创建了一个新的三元组来与muslc一起使用。这是实验性的,这就是为什么我们还没有把它直接引入vcpkg的原因。我们发现这个三元组的一个限制是boost locale现在不能用它编译。这导致我们切换了一些库,特别是使用 restinio http库 是我们能找到的少数几个用这个三元组编译的http之一。我们提供了另一个Dockerfile,它以Debian为目标,不使用任何实验特性。

那你今天为什么要尝试Alpine而不是Debian呢?如果我们看我们的图像,这就是我们在使用Debian时看到的图像大小。

docker image ls
REPOSITORY            TAG              IMAGE ID         CREATED            SIZE
findfaces/run         latest           0e20b1ff7f82     2 hours ago        161MB
findfaces/build       latest           7d1675936cdd     2 hours ago        6.5GB

您可以看到,我们的构建容器比运行时容器大得多,这显然是优化资源使用的理想选择。我们的应用程序是40MB,所以我们运行的基本映像是剩余的121MB。让我们把它和阿尔卑斯山比较一下。

REPOSITORY             TAG             IMAGE ID         CREATED            SIZE
findfaces/run          latest          0ef4c0b68551     2 hours ago        50.1MB
findfaces/build        latest          fa7fe2783c58     2 hours ago        5.57GB

构建容器没有多少节省,但是运行容器只比我们的应用程序大10MB。

摘要

这篇文章展示了如何在容器中使用C++代码。我们使用了一个多级构建容器,在其中我们在一个容器中编译资产,并在一个Dockerfile中使用运行时容器中的资产。这将产生一个针对大小进行优化的运行时容器。我们还使用vcpkg来获取我们正在使用的库的最新版本,而不是包管理器中提供的版本。通过静态链接这些库,我们减少了需要在运行时容器中管理的二进制文件的复杂性。我们使用Alpine发行版进一步确保最终的运行时容器与基于Debian的容器相比尽可能小。我们还将应用程序作为服务公开,使用http库在容器外部访问它。

接下来呢

我们计划在未来的岗位上继续寻找集装箱。我们将有一个很快向我们展示如何使用visualstudio和VS代码的容器从这篇文章。接下来我们将展示如何将这些容器部署到Azure。我们还将使用Windows容器重新访问这个应用程序。

给我们反馈

我们很想听听您的意见,您希望在未来看到集装箱的相关内容。我们更喜欢看到C++社区产生了自己关于使用C++容器的内容。现在这里的材料很少,我们相信在云中使用容器的C++潜力巨大。

一如既往,我们欢迎您的反馈。我们可以通过下面的评论或电子邮件联系我们( visualcpp@microsoft.com ). 如果您遇到其他问题或对visualstudio有任何建议,请联系我们 帮助>发送反馈>报告问题/在产品中提供建议 ,或 通过 开发者社区 . 你也可以在Twitter上找到我们( @视觉 ).

© 版权声明
THE END
喜欢就支持一下吧,技术咨询可以联系QQ407933975
点赞0 分享