構(gòu)建插件
如果你不想docker在你的構(gòu)建中直接調(diào)用,有一套豐富的 Maven 和 Gradle 插件可以為你完成這項工作。這里僅僅是少數(shù)。
Spring Boot Maven 和 Gradle 插件
您可以使用Maven和Gradle的 Spring Boot 構(gòu)建插件來創(chuàng)建容器映像。docker build這些插件使用Cloud Native Buildpacks創(chuàng)建一個 OCI 映像(與創(chuàng)建的格式相同) 。您不需要Dockerfile,但您確實需要 Docker 守護程序,可以在本地(使用 docker 構(gòu)建時使用)或通過DOCKER_HOST環(huán)境變量遠(yuǎn)程進行。默認(rèn)構(gòu)建器針對 Spring Boot 應(yīng)用程序進行了優(yōu)化,并且圖像像上面的示例一樣有效地分層。
以下示例在不更改pom.xml文件的情況下使用 Maven:
./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=myorg/myapp復(fù)制
以下示例適用于 Gradle,無需更改build.gradle文件:
./gradlew bootBuildImage --imageName=myorg/myapp復(fù)制
第一次構(gòu)建可能需要很長時間,因為它必須下載一些容器鏡像和 JDK,但后續(xù)構(gòu)建應(yīng)該很快。
然后您可以運行映像,如以下清單所示(帶輸出):
docker run -p 8080:8080 -t myorg/myapp
Setting Active Processor Count to 6
Calculating JVM memory based on 14673596K available memory
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx14278122K -XX:MaxMetaspaceSize=88273K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 14673596K, Thread Count: 50, Loaded Class Count: 13171, Headroom: 0%)
Adding 129 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -agentpath:/layers/paketo-buildpacks_bellsoft-liberica/jvmkill/jvmkill-1.16.0-RELEASE.so=printHeapHistogram=1 -XX:ActiveProcessorCount=6 -XX:MaxDirectMemorySize=10M -Xmx14278122K -XX:MaxMetaspaceSize=88273K -XX:ReservedCodeCacheSize=240M -Xss1M -Dorg.springframework.cloud.bindings.boot.enable=true
....
2015-03-31 13:25:48.035 INFO 1 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2015-03-31 13:25:48.037 INFO 1 --- [ main] hello.Application復(fù)制
您可以看到應(yīng)用程序正常啟動。您可能還注意到 JVM 內(nèi)存需求是在容器內(nèi)計算并設(shè)置為命令行選項的。這與多年來在 Cloud Foundry 構(gòu)建包中使用的內(nèi)存計算相同。它代表了對一系列 JVM 應(yīng)用程序(包括但不限于 Spring Boot 應(yīng)用程序)的最佳選擇的重要研究,結(jié)果通常比 JVM 的默認(rèn)設(shè)置好得多。您可以自定義命令行選項并通過設(shè)置環(huán)境變量覆蓋內(nèi)存計算器,如Paketo buildpacks 文檔中所示。
Spotify Maven 插件
Spotify Maven 插件是一個受歡迎的選擇。它要求您編寫 aDockerfile然后docker為您運行,就像您在命令行上執(zhí)行它一樣。docker 鏡像標(biāo)簽和其他東西有一些配置選項,但它使您的應(yīng)用程序中的 docker 知識集中在一個Dockerfile很多人喜歡的 .
對于真正的基本用法,它無需額外配置即可開箱即用:
mvn com.spotify:dockerfile-maven-plugin:build
...
[INFO] Building Docker context /home/dsyer/dev/demo/workspace/myapp
[INFO]
[INFO] Image will be built without a name
[INFO]
...
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 7.630 s
[INFO] Finished at: 2018-11-06T16:03:16+00:00
[INFO] Final Memory: 26M/595M
[INFO] ------------------------------------------------------------------------復(fù)制
這將構(gòu)建一個匿名 docker 映像。我們現(xiàn)在可以在命令行上標(biāo)記它docker或使用 Maven 配置將其設(shè)置為repository. 以下示例在不更改pom.xml文件的情況下工作:
$ mvn com.spotify:dockerfile-maven-plugin:build -Ddockerfile.repository=myorg/myapp復(fù)制
或者,您更改pom.xml?文件:
pom.xml
com.spotifydockerfile-maven-plugin1.4.8myorg/${project.artifactId}復(fù)制
Palantir Gradle 插件
Palantir Gradle 插件與 a 一起使用,Dockerfile并且還可Dockerfile以為您生成 a。然后它docker就像在命令行上運行它一樣運行。
首先,您需要將插件導(dǎo)入您的build.gradle:
build.gradle
buildscript {
...
dependencies {
...
classpath('gradle.plugin.com.palantir.gradle.docker:gradle-docker:0.13.0')
}
}復(fù)制
然后,最后,您可以應(yīng)用插件并調(diào)用它的任務(wù):
build.gradle
apply plugin: 'com.palantir.docker'
group = 'myorg'
bootJar {
baseName = 'myapp'
version = '0.1.0'
}
task unpack(type: Copy) {
dependsOn bootJar
from(zipTree(tasks.bootJar.outputs.files.singleFile))
into("build/dependency")
}
docker {
name "${project.group}/${bootJar.baseName}"
copySpec.from(tasks.unpack.outputs).into("dependency")
buildArgs(['DEPENDENCY': "dependency"])
}復(fù)制
在本例中,我們選擇將 Spring Boot fat JAR 解壓到build目錄中的特定位置,該位置是 docker build 的根目錄。Dockerfile然后早期顯示的多層(不是多階段)起作用。
Jib Maven 和 Gradle 插件
Google 有一個名為Jib的開源工具,它相對較新,但出于多種原因非常有趣。可能最有趣的是您不需要 docker 來運行它。Jib 使用與您獲得的相同標(biāo)準(zhǔn)輸出來構(gòu)建映像,docker build但除非您要求它,否則它不會使用docker,因此它可以在未安裝 docker 的環(huán)境中工作(在構(gòu)建服務(wù)器中很常見)。您也不需要Dockerfile(無論如何都會被忽略)或任何東西pom.xml來獲得在 Maven 中構(gòu)建的圖像(Gradle 將要求您至少在 中安裝插件build.gradle)。
DockerfileJib 的另一個有趣的特性是它對層有意見,并且它以與上面創(chuàng)建的多層略有不同的方式優(yōu)化它們。與胖 JAR 中一樣,Jib 將本地應(yīng)用程序資源與依賴項分開,但它更進一步,還將快照依賴項放入單獨的層,因為它們更有可能發(fā)生變化。有用于進一步自定義布局的配置選項。
以下示例在不更改 Maven 的情況下使用pom.xml:
$ mvn com.google.cloud.tools:jib-maven-plugin:build -Dimage=myorg/myapp復(fù)制
myorg要運行該命令,您需要具有在存儲庫前綴下推送到 Dockerhub 的權(quán)限。如果您已docker在命令行上進行了身份驗證,則可以在本地~/.docker配置中使用。~/.m2/settings.xml您還可以在您的(id存儲庫的重要)中設(shè)置 Maven“服務(wù)器”身份驗證:
settings.xml
registry.hub.docker.commyorg...復(fù)制
還有其他選項——例如,您可以docker使用dockerBuild目標(biāo)而不是build. 還支持其他容器注冊表。對于每一項,您都需要通過 Docker 或 Maven 設(shè)置來設(shè)置本地身份驗證。
gradle 插件具有類似的功能,一旦你在你的build.gradle:.
build.gradle
plugins {
...
id 'com.google.cloud.tools.jib' version '1.8.0'
}復(fù)制
以下清單使用入門指南中使用的舊 Gradle 樣式:
build.gradle
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
mavenCentral()
}
dependencies {
classpath('org.springframework.boot:spring-boot-gradle-plugin:2.2.1.RELEASE')
classpath('com.google.cloud.tools.jib:com.google.cloud.tools.jib.gradle.plugin:1.8.0')
}
}復(fù)制
然后,您可以通過運行以下命令來構(gòu)建映像:
./gradlew jib --image=myorg/myapp復(fù)制
與 Maven 構(gòu)建一樣,如果您已docker在命令行上進行了身份驗證,則圖像推送將從您的本地~/.docker配置進行身份驗證。
持續(xù)集成
如今,自動化(或應(yīng)該是)是每個應(yīng)用程序生命周期的一部分。人們用來進行自動化的工具往往非常擅長從源代碼調(diào)用構(gòu)建系統(tǒng)。因此,如果這為您提供了一個 docker 映像,并且構(gòu)建代理中的環(huán)境與開發(fā)人員自己的環(huán)境充分一致,那可能就足夠了。對 docker 注冊表進行身份驗證可能是最大的挑戰(zhàn),但所有自動化工具中都有一些功能可以幫助解決這個問題。
但是,有時最好將容器創(chuàng)建完全留給自動化層,在這種情況下,可能不需要污染用戶的代碼。容器創(chuàng)建很棘手,開發(fā)人員有時不需要真正關(guān)心它。如果用戶代碼更干凈,則不同的工具更有可能“做正確的事”(應(yīng)用安全修復(fù)、優(yōu)化緩存等)。自動化有多種選擇,如今它們都帶有一些與容器相關(guān)的功能。我們將看一對夫婦。
大廳
Concourse是一個基于管道的自動化平臺,可用于 CI 和 CD。它在 VMware 內(nèi)部使用,該項目的主要作者在那里工作。Concourse 中的所有內(nèi)容都是無狀態(tài)的,并且在容器中運行,CLI 除外。由于運行容器是自動化管道的主要業(yè)務(wù)順序,因此很好地支持創(chuàng)建容器。Docker Image Resource負(fù)責(zé)保持構(gòu)建的輸出狀態(tài)是最新的,如果它是一個容器鏡像的話。
以下示例管道為前面顯示的示例構(gòu)建了一個 docker 映像,假設(shè)它位于 github 中myorg/myapp,Dockerfile在根中有一個,并且在 中有一個構(gòu)建任務(wù)聲明src/main/ci/build.yml:
resources:
- name: myapp
type: git
source:
uri: https://github.com/myorg/myapp.git
- name: myapp-image
type: docker-image
source:
email: {{docker-hub-email}}
username: {{docker-hub-username}}
password: {{docker-hub-password}}
repository: myorg/myapp
jobs:
- name: main
plan:
- task: build
file: myapp/src/main/ci/build.yml
- put: myapp-image
params:
build: myapp復(fù)制
管道的結(jié)構(gòu)是非常具有聲明性的:您定義“資源”(輸入、輸出或兩者)和“作業(yè)”(使用資源并將操作應(yīng)用于資源)。如果任何輸入資源發(fā)生更改,則會觸發(fā)新的構(gòu)建。如果任何輸出資源在作業(yè)期間發(fā)生更改,則會對其進行更新。
管道可以在與應(yīng)用程序源代碼不同的地方定義。此外,對于通用構(gòu)建設(shè)置,任務(wù)聲明也可以集中或外部化。這允許在開發(fā)和自動化之間分離一些關(guān)注點,這適合一些軟件開發(fā)組織。
詹金斯
Jenkins是另一個流行的自動化服務(wù)器。它具有大量功能,但最接近此處其他自動化示例的是管道功能。下面Jenkinsfile使用 Maven 構(gòu)建一個 Spring Boot 項目,然后使用 aDockerfile構(gòu)建一個鏡像并將其推送到存儲庫:
Jenkinsfile
node {
checkout scm
sh './mvnw -B -DskipTests clean package'
docker.build("myorg/myapp").push()
}復(fù)制
對于需要在構(gòu)建服務(wù)器中進行身份驗證的(實際)docker 存儲庫,您可以docker使用docker.withCredentials(…?).
構(gòu)建包
packSpring Boot Maven 和 Gradle 插件使用構(gòu)建包的方式與CLI 在以下示例中的使用方式完全相同。給定相同的輸入,生成的圖像是相同的。
Cloud Foundry在內(nèi)部使用容器已經(jīng)很多年了,用于將用戶代碼轉(zhuǎn)換為容器的部分技術(shù)是 Build Packs,這個想法最初是從Heroku借來的。當(dāng)前一代的 buildpacks (v2) 生成由平臺組裝到容器中的通用二進制輸出。新一代構(gòu)建包(v3) 是 Heroku 與其他公司(包括 VMware)的合作,它直接明確地構(gòu)建容器鏡像。這對開發(fā)人員和運營商來說很有趣。開發(fā)人員不需要太關(guān)心如何構(gòu)建容器的細(xì)節(jié),但如果需要,他們可以輕松創(chuàng)建一個。Buildpacks 還具有許多用于緩存構(gòu)建結(jié)果和依賴項的功能。通常,構(gòu)建包的運行速度比原生 Docker 構(gòu)建快得多。操作員可以掃描容器以審核其內(nèi)容并將其轉(zhuǎn)換為修補它們以進行安全更新。此外,您可以在本地(例如,在開發(fā)人員機器或 CI 服務(wù)中)或在 Cloud Foundry 等平臺中運行構(gòu)建包。
buildpack 生命周期的輸出是容器映像,但您不需要Dockerfile. 輸出映像中的文件系統(tǒng)層由 buildpack 控制。通常,許多優(yōu)化都是在開發(fā)人員不必知道或關(guān)心它們的情況下進行的。在較低層(例如包含操作系統(tǒng)的基礎(chǔ)映像)和較高層(包含中間件和語言特定依賴項)之間還有一個應(yīng)用程序二進制接口。這使得 Cloud Foundry 等平臺可以在有安全更新的情況下修補較低層,而不會影響應(yīng)用程序的完整性和功能。
為了讓您了解 buildpack 的功能,以下示例(顯示其輸出)從命令行使用Pack CLI(它可以與我們在本指南中使用的示例應(yīng)用程序一起使用 - 不需要Dockerfile或任何特殊的構(gòu)建配置):
pack build myorg/myapp --builder=paketobuildpacks/builder:base --path=.
base: Pulling from paketobuildpacks/builder
Digest: sha256:4fae5e2abab118ca9a37bf94ab42aa17fef7c306296b0364f5a0e176702ab5cb
Status: Image is up to date for paketobuildpacks/builder:base
base-cnb: Pulling from paketobuildpacks/run
Digest: sha256:a285e73bc3697bc58c228b22938bc81e9b11700e087fd9d44da5f42f14861812
Status: Image is up to date for paketobuildpacks/run:base-cnb
===> DETECTING
7 of 18 buildpacks participating
paketo-buildpacks/ca-certificates 2.3.2
paketo-buildpacks/bellsoft-liberica 8.2.0
paketo-buildpacks/maven 5.3.2
paketo-buildpacks/executable-jar 5.1.2
paketo-buildpacks/apache-tomcat 5.6.1
paketo-buildpacks/dist-zip 4.1.2
paketo-buildpacks/spring-boot 4.4.2
===> ANALYZING
Previous image with name "myorg/myapp" not found
===> RESTORING
===> BUILDING
Paketo CA Certificates Buildpack 2.3.2
https://github.com/paketo-buildpacks/ca-certificates
Launch Helper: Contributing to layer
Creating /layers/paketo-buildpacks_ca-certificates/helper/exec.d/ca-certificates-helper
Paketo BellSoft Liberica Buildpack 8.2.0
https://github.com/paketo-buildpacks/bellsoft-liberica
Build Configuration:
$BP_JVM_VERSION 11 the Java version
Launch Configuration:
$BPL_JVM_HEAD_ROOM 0 the headroom in memory calculation
$BPL_JVM_LOADED_CLASS_COUNT 35% of classes the number of loaded classes in memory calculation
$BPL_JVM_THREAD_COUNT 250 the number of threads in memory calculation
$JAVA_TOOL_OPTIONS the JVM launch flags
BellSoft Liberica JDK 11.0.12: Contributing to layer
Downloading from https://github.com/bell-sw/Liberica/releases/download/11.0.12+7/bellsoft-jdk11.0.12+7-linux-amd64.tar.gz
Verifying checksum
Expanding to /layers/paketo-buildpacks_bellsoft-liberica/jdk
Adding 129 container CA certificates to JVM truststore
Writing env.build/JAVA_HOME.override
Writing env.build/JDK_HOME.override
BellSoft Liberica JRE 11.0.12: Contributing to layer
Downloading from https://github.com/bell-sw/Liberica/releases/download/11.0.12+7/bellsoft-jre11.0.12+7-linux-amd64.tar.gz
Verifying checksum
Expanding to /layers/paketo-buildpacks_bellsoft-liberica/jre
Adding 129 container CA certificates to JVM truststore
Writing env.launch/BPI_APPLICATION_PATH.default
Writing env.launch/BPI_JVM_CACERTS.default
Writing env.launch/BPI_JVM_CLASS_COUNT.default
Writing env.launch/BPI_JVM_SECURITY_PROVIDERS.default
Writing env.launch/JAVA_HOME.default
Writing env.launch/MALLOC_ARENA_MAX.default
Launch Helper: Contributing to layer
Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/active-processor-count
Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/java-opts
Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/link-local-dns
Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/memory-calculator
Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/openssl-certificate-loader
Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/security-providers-configurer
Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/security-providers-classpath-9
JVMKill Agent 1.16.0: Contributing to layer
Downloading from https://github.com/cloudfoundry/jvmkill/releases/download/v1.16.0.RELEASE/jvmkill-1.16.0-RELEASE.so
Verifying checksum
Copying to /layers/paketo-buildpacks_bellsoft-liberica/jvmkill
Writing env.launch/JAVA_TOOL_OPTIONS.append
Writing env.launch/JAVA_TOOL_OPTIONS.delim
Java Security Properties: Contributing to layer
Writing env.launch/JAVA_SECURITY_PROPERTIES.default
Writing env.launch/JAVA_TOOL_OPTIONS.append
Writing env.launch/JAVA_TOOL_OPTIONS.delim
Paketo Maven Buildpack 5.3.2
https://github.com/paketo-buildpacks/maven
Build Configuration:
$BP_MAVEN_BUILD_ARGUMENTS -Dmaven.test.skip=true package the arguments to pass to Maven
$BP_MAVEN_BUILT_ARTIFACT target/*.[jw]ar the built application artifact explicitly. Supersedes $BP_MAVEN_BUILT_MODULE
$BP_MAVEN_BUILT_MODULE the module to find application artifact in
Creating cache directory /home/cnb/.m2
Compiled Application: Contributing to layer
Executing mvnw --batch-mode -Dmaven.test.skip=true package
[ ... Maven build output ... ]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 53.474 s
[INFO] Finished at: 2021-07-23T20:10:28Z
[INFO] ------------------------------------------------------------------------
Removing source code
Paketo Executable JAR Buildpack 5.1.2
https://github.com/paketo-buildpacks/executable-jar
Class Path: Contributing to layer
Writing env/CLASSPATH.delim
Writing env/CLASSPATH.prepend
Process types:
executable-jar: java org.springframework.boot.loader.JarLauncher (direct)
task: java org.springframework.boot.loader.JarLauncher (direct)
web: java org.springframework.boot.loader.JarLauncher (direct)
Paketo Spring Boot Buildpack 4.4.2
https://github.com/paketo-buildpacks/spring-boot
Creating slices from layers index
dependencies
spring-boot-loader
snapshot-dependencies
application
Launch Helper: Contributing to layer
Creating /layers/paketo-buildpacks_spring-boot/helper/exec.d/spring-cloud-bindings
Spring Cloud Bindings 1.7.1: Contributing to layer
Downloading from https://repo.spring.io/release/org/springframework/cloud/spring-cloud-bindings/1.7.1/spring-cloud-bindings-1.7.1.jar
Verifying checksum
Copying to /layers/paketo-buildpacks_spring-boot/spring-cloud-bindings
Web Application Type: Contributing to layer
Reactive web application detected
Writing env.launch/BPL_JVM_THREAD_COUNT.default
4 application slices
Image labels:
org.opencontainers.image.title
org.opencontainers.image.version
org.springframework.boot.version
===> EXPORTING
Adding layer 'paketo-buildpacks/ca-certificates:helper'
Adding layer 'paketo-buildpacks/bellsoft-liberica:helper'
Adding layer 'paketo-buildpacks/bellsoft-liberica:java-security-properties'
Adding layer 'paketo-buildpacks/bellsoft-liberica:jre'
Adding layer 'paketo-buildpacks/bellsoft-liberica:jvmkill'
Adding layer 'paketo-buildpacks/executable-jar:classpath'
Adding layer 'paketo-buildpacks/spring-boot:helper'
Adding layer 'paketo-buildpacks/spring-boot:spring-cloud-bindings'
Adding layer 'paketo-buildpacks/spring-boot:web-application-type'
Adding 5/5 app layer(s)
Adding layer 'launcher'
Adding layer 'config'
Adding layer 'process-types'
Adding label 'io.buildpacks.lifecycle.metadata'
Adding label 'io.buildpacks.build.metadata'
Adding label 'io.buildpacks.project.metadata'
Adding label 'org.opencontainers.image.title'
Adding label 'org.opencontainers.image.version'
Adding label 'org.springframework.boot.version'
Setting default process type 'web'
Saving myorg/myapp...
*** Images (ed1f92885df0):
myorg/myapp
Adding cache layer 'paketo-buildpacks/bellsoft-liberica:jdk'
Adding cache layer 'paketo-buildpacks/maven:application'
Adding cache layer 'paketo-buildpacks/maven:cache'
Successfully built image 'myorg/myapp'復(fù)制
這--builder是一個運行 buildpack 生命周期的 Docker 鏡像。通常,它將是所有開發(fā)人員或單個平臺上的所有開發(fā)人員的共享資源。您可以在命令行上設(shè)置默認(rèn)構(gòu)建器(在 中創(chuàng)建一個文件~/.pack),然后從后續(xù)構(gòu)建中省略該標(biāo)志。
構(gòu)建器
paketobuildpacks/builder:base還知道如何從可執(zhí)行 JAR 文件構(gòu)建映像,因此您可以先使用 Maven 構(gòu)建,然后將其指向--pathJAR 文件以獲得相同的結(jié)果。
原生
容器和平臺領(lǐng)域的另一個新項目是Knative。如果您不熟悉它,可以將其視為構(gòu)建無服務(wù)器平臺的構(gòu)建塊。它建立在Kubernetes 之上,因此最終它會使用容器鏡像并將它們轉(zhuǎn)化為平臺上的應(yīng)用程序或“服務(wù)”。不過,它的主要功能之一是能夠使用源代碼并為您構(gòu)建容器,使其對開發(fā)人員和操作員更加友好。Knative Build是執(zhí)行此操作的組件,它本身就是一個靈活的平臺,用于將用戶代碼轉(zhuǎn)換為容器——您幾乎可以以任何您喜歡的方式進行操作。一些模板提供了通用模式(例如 Maven 和 Gradle 構(gòu)建)和多階段 docker 構(gòu)建使用卡尼科。還有一個模板使用了Buildpacks,這對我們來說很有趣,因為 buildpacks 一直對 Spring Boot 有很好的支持。
結(jié)束
本指南提供了許多用于為 Spring Boot 應(yīng)用程序構(gòu)建容器映像的選項。所有這些都是完全有效的選擇,現(xiàn)在由您決定您需要哪一個。您的第一個問題應(yīng)該是“我真的需要構(gòu)建容器映像嗎?” 如果答案是“是”,那么您的選擇可能會受到效率、可緩存性和關(guān)注點分離的驅(qū)動。您是否想讓開發(fā)人員無需過多了解容器鏡像的創(chuàng)建方式?當(dāng)需要修補操作系統(tǒng)和中間件漏洞時,您是否想讓開發(fā)人員負(fù)責(zé)更新映像?或者,開發(fā)人員可能需要完全控制整個過程,并且他們擁有所需的所有工具和知識。
審核編輯:湯梓紅
-
容器
+關(guān)注
關(guān)注
0文章
494瀏覽量
22044 -
spring
+關(guān)注
關(guān)注
0文章
338瀏覽量
14310 -
Docker
+關(guān)注
關(guān)注
0文章
454瀏覽量
11814
發(fā)布評論請先 登錄
相關(guān)推薦
評論