本文是我们学院课程中名为《 面向Java开发人员的Docker教程 》的一部分。
在本课程中,我们提供了一系列教程,以便您可以开发自己的基于Docker的应用程序。 我们涵盖了广泛的主题,从通过命令行的Docker到开发,测试,部署和持续集成。 通过我们简单易懂的教程,您将能够在最短的时间内启动并运行自己的项目。 在这里查看 !
1.简介
在本教程中,我们已经了解了Docker如何渗透到典型Java应用程序生命周期的各个方面:构建,开发,测试和部署。 在这一部分中,我们将专注于持续集成中日益重要的主题之一。
尽管持续集成(和持续交付)解决方案的市场人满为患,但不管好坏,有一种产品已经停滞了多年,并且非常接近那里的每个Java开发人员的核心。 是的,您的猜测是正确的,它是领先的开源自动化服务器Jenkins 。
2.为什么选择詹金斯?
对于许多人而言,与詹金斯的关系属于仇恨或爱情类别。 抛开历史,第二代Jenkins平台即2.x
版本分支像龙卷风一样袭击了生态系统,从字面上变成了改变游戏规则的人。
明确地说 ,这是詹金斯(Jenkins)的主要功能,这使其与许多其他竞争解决方案区分开来:
- 内置对交付管道(也称为“代码管道”)的支持
- 改进的用户界面和可用性
- 非常容易安装和配置
- 出色的可扩展性和非常丰富的插件生态系统
- 分布式部署
- 自动化
为了说明其中的大部分内容,我们将把到目前为止所学到的关于Docker的知识付诸实践,并构建我们自己的Jenkins映像。 不仅如此,我们还将其与Git集成(使用非常流行的Github托管),配置工具,为我们先前开发的 Spring Boot应用程序创建多分支管道。 最后但并非最不重要的一点是,我们将以完全自动化的方式来完成所有这些工作。 如果听起来令人兴奋,让我们开始吧。
3.管道作为代码
业界的最新趋势之一是将连续交付管道视为代码,将其存储在源代码控制系统中并在必要时进行版本控制。 如果您想在项目中引入类似的东西,毫无疑问, 詹金斯是完美的选择。
Jenkins支持开箱即用的管道配置有多种优势。 我们将在本教程的此部分中使用的一个方法是将管道定义Jenkinsfile以及您的项目保留在存储库树的根目录中。
您可能还记得 ,我们的Spring Boot应用程序需要Java,并且正在使用Gradle进行依赖项管理。 请记住,这里是一个示例,我们可以将其作为在Jenkins中构建此类项目的简单管道。
pipeline {
agent any
options {
disableConcurrentBuilds()
buildDiscarder(logRotator(numToKeepStr:'5'))
}
triggers {
pollSCM('H/15 * * * *')
}
tools {
jdk "jdk-8u162"
}
environment {
JAVA_HOME="${jdk}"
}
stages {
stage('Cleanup before build') {
steps {
cleanWs()
}
}
stage('Checkout from Github') {
steps {
checkout scm
}
}
stage('Build') {
steps {
script {
def rtGradle = Artifactory.newGradleBuild()
rtGradle.tool = "Gradle 4.3.0"
rtGradle.run buildFile: 'build.gradle', tasks: 'clean build'
}
}
}
}
post {
always {
archiveArtifacts artifacts: 'build/libs/*.jar', fingerprint: true
}
}
}
我希望您会同意,它非常干净和富有表现力。 我们唯一需要做的就是将该定义与相关项目共同定位。
如果您希望看到我们的Jenkins部署正在进行中 ,那么此时最好在其中包含Spring Boot应用程序的情况下创建一个新的Github (或Bitbucket , Gitlab …)存储库。
4. Jenkins和Docker
幸运的是, Jenkins维护着官方的Docker映像存储库 ,因此可以很容易地对其进行集成和扩展以满足您的需求。 到目前为止, Jenkins的最新长期支持版本是2.89.4
,这是我们将在本教程的此部分中使用的。
有趣的是, Jenkins还提供了全面的Docker支持(作为管道声明和工作定义的一部分),我们也将对其进行研究。
开箱即用的官方图像可以轻松扩展您的图像,例如,扩展其中一个图像。
FROM jenkins/jenkins:lts
好的开始,但是预装一些我们认为对我们有用的插件将是很棒的。 使用已经提供的install-plugins.sh
脚本很简单。
RUN /usr/local/bin/install-plugins.sh docker-plugin docker-slaves workflow-scm-step workflow-cps pipeline-model-definition docker-workflow cloudbees-folder timestamper workflow-aggregator git gradle pipeline-maven artifactory maven-plugin ssh-slaves build-timeout pipeline-stage-view antisamy-markup-formatter mailer matrix-auth junit findbugs maven-invoker-plugin pipeline-build-step credentials ws-cleanup email-ext ldap pam-auth subversion blueocean credentials-binding
老实说,这里列出了很多插件,但这只是可用的一小部分。 Jenkins周围的插件生态系统令人惊讶。 此时,我们需要生成一个密钥对,以与Github (或Bitbucket , Gitlab …)存储库一起使用。
ssh-keygen -t rsa -C jenkins@javacodegeeks.com
并将此密钥对复制到我们的图像中:
COPY github/id_rsa /var/jenkins_home/.ssh/
COPY github/id_rsa.pub /var/jenkins_home/.ssh/
继续前进,我们当然希望使用安全协议HTTPS运行Jenkins ,因此我们将必须生成一个自签名证书以及私钥。
openssl req -x509 -sha256 -newkey rsa:2048 -keyout jenkins.key -out jenkins.crt -days 1024 -nodes
openssl rsa -in jenkins.key -out jenkins.rsa
让我们也将生成的文件添加到图像中:
COPY jenkins/jenkins.crt /var/lib/jenkins/cert
COPY jenkins/jenkins.rsa /var/lib/jenkins/pk
太好了,我们已经完成了环境部分。 让我们用几个环境变量来完成它。
ENV JENKINS_SLAVE_AGENT_PORT 50001
ENV JAVA_OPTS -Djenkins.install.runSetupWizard=false
ENV JENKINS_OPTS --httpPort=-1 --httpsPort=8083 --httpsCertificate=/var/lib/jenkins/cert --httpsPrivateKey=/var/lib/jenkins/pk
为简单起见,最好预先配置管理帐户,这样我们就可以使用我们已知的凭据立即登录。
ENV JENKINS_USER admin
ENV JENKINS_PASS ef8a7543087c4b999cbecdd57696f557
此外,最好知道URL以连接到与Git兼容的存储库并获取我们将要构建的项目。
ENV GIT_URL "<Git URL to repo>"
接下来是重要的,但不是很明显。 我们需要用户名和密码才能下载Oracle JDK的官方发行版(众所周知,在执行此操作之前,应接受许可协议)。
ENV ORACLE_JDK_USER "<username>"
ENV ORACLE_JDK_PASSWORD "<password>"
如果仅在此阶段构建映像并运行其中的容器,那么最终将获得功能齐全的Jenkins实例,但未配置任何工具,安全性或作业。 由于我们的目标是实现完全自动化,因此我们将使用Jenkins脚本(基于Groovy )功能来实现这一目标。
该脚本的简单示例是理解Jenkins自动化背后的核心思想的一个很好的起点,这里是init.groovy
。
def instance = Jenkins.getInstance()
instance.setNumExecutors(5)
instance.setSlaveAgentPort([55001])
它看起来一点也不可怕。 逐步添加更多的内容,让我们看一下下一个配置安全性设置以访问Jenkins实例security.groovy
脚本。
// Get system environment
def env = System.getenv()
def instance = Jenkins.getInstance()
instance.setSecurityRealm(new HudsonPrivateSecurityRealm(false))
instance.setAuthorizationStrategy(new GlobalMatrixAuthorizationStrategy())
def user = instance.getSecurityRealm().createAccount(env.JENKINS_USER, env.JENKINS_PASS)
user.save()
instance.getAuthorizationStrategy().add(Jenkins.ADMINISTER, env.JENKINS_USER)
instance.save()
您还可以从上面的Dockerfile定义中发现我们如何使用JENKINS_USER
和JENKINS_PASS
环境变量。 展望未来,我们要做的下一件事是存储私钥(我们之前已经复制到图像)来访问SCM我们的选择,在脚本credentials.groovy
。
def instance = Jenkins.getInstance()
String keyfile = "/var/jenkins_home/.ssh/id_rsa"
def domain = Domain.global()
def store = instance.getExtensionList('com.cloudbees.plugins.credentials.SystemCredentialsProvider')[0].getStore()
def credentials = new BasicSSHUserPrivateKey(
CredentialsScope.GLOBAL,
"22d9d94b-2794-4d0c-8576-accf87764d0f",
"jenkins",
new BasicSSHUserPrivateKey.FileOnMasterPrivateKeySource(keyfile),
"",
""
)
store.addCredentials(domain, credentials)
由于我们决定专注于Spring Boot应用程序,并且已经为此准备了一个Jenkinsfile ,预装Oracle JDK 8 (最新版本为8u162
)和Gradle工具(我们使用的版本为4.3.0
)会很好。不是最新的,但让我们坚持下去。 jdk.groovy
代码段负责Oracle JDK配置。
def env = System.getenv()
def instance = Jenkins.getInstance()
def descriptor = instance.getDescriptor("hudson.model.JDK")
def installations = []
def installer = new JDKInstaller("jdk-8u162-oth-JPR", true)
def installerProps = new InstallSourceProperty([installer])
def installation = new JDK("jdk-8u162", "", [installerProps])
installations.push(installation)
descriptor.setInstallations(installations.toArray(new JDK[0]))
descriptor.save()
def jdkInstaller = instance.getDescriptor("hudson.tools.JDKInstaller")
jdkInstaller.doPostCredential(env.ORACLE_JDK_USER, env.ORACLE_JDK_PASSWORD)
请注意配置用户名和密码以下载JDK (使用ORACLE_JDK_USER
和ORACLE_JDK_PASSWORD
环境变量)的强制性步骤。 另一方面, gradle.groovy
看起来要简单得多。
def instance = Jenkins.getInstance()
def gradle = new GradleInstallation("Gradle 4.3.0", "", [new InstallSourceProperty([new GradleInstaller("4.3.0")])])
def descriptor = instance.getDescriptorByType(GradleInstallation.DescriptorImpl)
descriptor.setInstallations(gradle)
descriptor.save()
到目前为止,还很不错,但是最有趣的脚本仍保留在最后。 它是存储在pipeline.groovy
的管道配置。
def env = System.getenv()
def instance = Jenkins.getInstance()
def project = instance.createProject(WorkflowMultiBranchProject, 'spring-boot-webapp-pipeline')
GitSCMSource gitSCMSource = new GitSCMSource(null, env.GIT_URL, "22d9d94b-2794-4d0c-8576-accf87764d0f", "*", "", false)
project.getSourcesList().add(new BranchSource(gitSCMSource))
请注意,我们如何通过使用唯一标识符22d9d94b-2794-4d0c-8576-accf87764d0f
预先配置的credentials.groovy
。 Git存储库的URL来自环境变量GIT_URL
。
最后,让我们将所有脚本复制到映像中,让Jenkins有机会在初始化阶段执行它们。
COPY scripts/init.groovy /usr/share/jenkins/ref/init.groovy.d/init.groovy
COPY scripts/credentials.groovy /usr/share/jenkins/ref/init.groovy.d/credentials.groovy
COPY scripts/pipeline.groovy /usr/share/jenkins/ref/init.groovy.d/pipeline.groovy
COPY scripts/security.groovy /usr/share/jenkins/ref/init.groovy.d/security.groovy
COPY scripts/jdk.groovy /usr/share/jenkins/ref/init.groovy.d/jdk.groovy
COPY scripts/gradle.groovy /usr/share/jenkins/ref/init.groovy.d/gradle.groovy
太棒了,我们唯一需要手动执行的操作(但仅一次)是授权github/id_rsa.pub
密钥对您的Github (或Bitbucket , Gitlab …)存储库具有只读访问权限。 完成后,我们可以继续构建图像:
docker build . --tag jenkins:jcg
并立即运行我们的Jenkins容器。
docker run --rm -p 8083:8083 -p 50001:50001 -v /var/run/docker.sock:/var/run/docker.sock --privileged jenkins:jcg
容器启动后,我们可以导航到https:// localhost:8083并使用用户名admin
和密码ef8a7543087c4b999cbecdd57696f557
登录,扫描管道,等待构建,并检查我们的spring-boot-webapp-pipeline
作业状态。
一切看起来都是绿色的,因为任何健康的项目都应该一直都在寻找。 确保正确配置我们的工具将是很好的。
同样,验证输出管道是否配置了正确的存储库并指向要使用的正确凭据也无济于事。
最好的确认事情已经正确到位的方法是检查管道及其所有阶段是否已成功执行。
如果您正在寻找现代,更高级的Web UI, Jenkins会为Blue Ocean提供这种用户体验,而Blue Ocean是专为Jenkins Pipeline设计的 。
在这一点上,我们本来可以宣布成功,但是,老实说,安装工具的需求以及使自动化过程变得复杂的东西。 我们有可能使它变得更简单吗? 是的,可以肯定的是,这里的关键参与者再次是Docker 。
5. Jenkins中的Docker
正如我们已经提到的, Jenkins具有出色的Docker支持,可以通过相当数量的插件来实现,其中Docker插件和Docker Pipeline插件是关键。 实际上,这意味着您的管道(或其他类型的工作)可能会旋转容器以在其上运行构建(以及任何其他任务或阶段)。
证明差异的最好方法是修改上一节中的Jenkinsfile以使用Docker代理而不是预定义的工具。
pipeline {
agent {
docker {
image 'gradle:4.3.0-jdk8-alpine'
}
}
options {
disableConcurrentBuilds()
buildDiscarder(logRotator(numToKeepStr:'5'))
}
triggers {
pollSCM('H/15 * * * *')
}
stages {
stage('Cleanup before build') {
steps {
cleanWs()
}
}
stage('Checkout from Github') {
steps {
checkout scm
}
}
stage('Build') {
steps {
sh 'gradle build'
}
}
}
post {
always {
archiveArtifacts artifacts: 'build/libs/*.jar', fingerprint: true
}
}
}
该管道看起来更紧凑。 我们摆脱了很多定义(实际上还可以摆脱一些初始化脚本)。 当我们将这个新的Jenkinsfile提交到我们现有的存储库(覆盖当前存储库)时,我们应该观察到一些不同的阶段集,但是构建仍然是绿色的。
这只是Docker与Jenkins集成的一个示例,您可以利用多种不同的方式利用Docker来完善持续集成(和交付)流程的极限。
在结束讨论之前,这里还要提到一件事。 因为我们正在运行詹金斯在码头工人 ,和詹金斯本身执行工作/阶段步骤泊坞窗 ,容器使用的这种模式被称为Docker-in-Docker
(或不久DinD
)。 JérômePetazzoni在博客文章中精彩总结了运行DinD
环境时需要注意的很多事情。 如果您正在认真考虑此模型,请检查一下。
6。结论
在本教程的最后一部分中,我们通过完全自动化的方式部署和配置持续集成平台(基于Jenkins ),研究了Docker的相当实际的应用。 我们为之前开发的应用程序之一创建了构建管道,并演示了在实时Docker容器中运行Docker的用例。
这样,我们的教程就结束了。 这是一个漫长但希望有用的旅程,我们学到了一些实用而有用的东西。 通过重申Docker是一项了不起的技术,能够将我们推向新的视野,可以很好地总结整个系列的结论。
完整的脚本和项目源可供下载:
翻译自: https://www.javacodegeeks.com/2018/02/docker-java-developers-continuous-integration-docker.html