OpenShift V3 应用发布部署的简单场景演示


【编者的话】本文是一篇关于OpenShift如何应用在DevOps方面的文章。全文由浅入深,由易到难,通过3种不同场景下的部署,给读者一个对OpenShift功能的全新了解。译者以前和有些做容器开发,部署相关工作的同学认为:OpenShift仅仅是在Kubernetes基础之上架设了自己的WebUI,对API和CLI等接口进行了自己的封装(很多kubectl的命令都被oc继承)。但是在接触了OpenShift 1个月后(翻译本文时,我正好接触OpenShift一个月整),我对OpenShift有了全新的认识,特别是读过此文原文后,让我认识到OpenShift之所以能够作为一个PaaS的真正价值所在。

我以前有幸接触过IBM的Bulemix(CloudFoundry),了解过一些基础概念和它大概的运行原理。这段时间经过我对OpenShift的了解,似乎找到了他们之间都作为PaaS的共通之处,也就是它区别于Kubernetes这种容器编排系统的最主要的地方。

【上海站|3天烧脑式微服务架构训练营】培训内容包括:DevOps、微服务、Spring Cloud、Eureka、Ribbon、Feign、Hystrix、Zuul、Spring Cloud Config、Spring Cloud Sleuth等。

1.jpg

概览

如今,对很多公司来说最困难的事情之一就是发布管理。当然我们有很多的方法、工具和技术,但是如何把它们联系在一起并且跨组织功能的工作呢?一个产品的发布不是一个team的事,它会涉及到公司的每一个人。很多公司挣扎于此但效果并不好。这个问题在过去还可以勉强容忍,但现就在就没那么幸运了。当今的公司赖以生存的不仅是创新的能力,而且还有把创新变成产品的能力。我认为创新是比较容易的部分, 在受控的方式下,提供这些创新的产品和服务才是真正的挑战。

通过转移到微服务(micro-services)架构和基于容器技术例如Docker,可以简化或者合理化很多方面的技术。但在OpenShift以前没有一个平台能把它们结合到一起,OpenShift的出现就像隧道尽头的一束光,它允许开发和运维团队在一起工作,而且仍然让他们维护各自关注和控制的领域。在这篇文章中,我们将会看用OpenShift部署应用程序的三个场景,每个场景都会涉及到开发和运维。并且你会从中得到如何使用OpenShift的启发。这些仅仅是一些基本的场景我们也只是蜻蜓点水般带过,但却可以作为在容器世界里应用程序部署或者发布管理的一个起点。

场景 1:开发团队使用运维团队提供的镜像

大多数的运维团队都想控制应用程序的运行环境。并且确保应用程序的运行环境符合所有的安全规则,提供需要的功能并且定期更新。

开发团队关注于创新通过应用程序的功能性,稳定性和性能。

OpenShift允许在两个团队关注各自核心职责的同时,提供一个方法去整合不同团队的输入和输出,最后提供一个端到端的产品发布流程。

在OpenShift里有很多的方法去整合DevOps(开发运维)团队。一个简单的方法就是把开发和运维分开到不同的项目,并且允许开发的应用程序运行在运维提供的环境中,我们将用一个ruby写的hello-world程序作为例子去说明这种情况。

创建项目

为ruby应用创建开发和运维的项目
# oc login -u admin
# oc new-project ruby-ops
# oc new-project ruby-dev 

创建用户

# htpasswd /etc/origin/master/htpasswd dev
# htpasswd /etc/origin/master/htpasswd ops

设置权限

创建三个组ops-edit、dev-view和dev-edit,授予运维组编辑ruby-ops项目的权限,授予开发查看ruby-ops项目和编辑ruby-dev项目的权限。并且让ruby-dev项目可以从拉取ruby-ops项目拉取images。

创建用户和其对应的组
# oadm groups new ops-edit && oadm groups new dev-view && oadm groups new dev-edit
# oadm groups add-users ops-edit ops && oadm groups add-users dev-view dev && \
oadm groups add-users dev-edit dev

关联组到相关的项目,并授权ruby-dev项目可以从ruby-ops拉取images。
# oadm policy add-role-to-group edit ops-edit -n ruby-ops && \
# oadm policy add-role-to-group view dev-view -n ruby-ops && \
# oadm policy add-role-to-group edit dev-edit -n ruby-dev && \
# oadm policy add-role-to-group system:image-puller system:serviceaccounts:ruby-dev -n ruby-ops

Ruby的运行环境

作为运维用户用测试代码创建ruby运行时的image。
# oc login -u ops
# oc project ruby-ops
# oc new-app centos/ruby-22-centos7~https://github.com/openshift/ruby-hello-world.git 

创建应用程序需要的MySQL数据库,并把服务名设置为database。
# oc new-app mysql-ephemeral -p DATABASE_SERVICE_NAME=database
# oc env dc database --list | oc env dc ruby-hello-world -e -

场景 2:生产项目拉取开发项目产生的镜像

如果开发的应用程序根据所需的功能有不同的版本,并且通过了所有的测试,那么就可以部署到其他环境。通常先是质量和测试,然后生成最终的生产版本。在这个简单的例子中,我们用ticket-moster程序,把它从开发环境直接部署到生产环境中。

我们用到了一些场景1中熟悉的概念,这个技术依靠生产环境能够拉取在开发环境中产生的images。在这个场景中我们创建一个了配置并设置一个触发器,它能够在开发更新image之后自动的部署这个带有特定名字或者标签的images到生产环境中。场景1中,这个过程是手动完成的,在场景2中是自动的,在场景3中,我们将看到如何用Jenkins去部署一个比较复杂的,带逐层审批的pipeline流程。

创建项目并设置拉取权限

# oc new-project ticket-monster-dev
# oc new-project ticket-monster-prod
# oc policy add-role-to-group system:image-puller system:serviceaccounts:ticket-monster-prod -n ticket-monster-dev

创建ticket monster项目的开发环境模板

# vi monster.yaml
kind: Template
apiVersion: v1
metadata:
name: monster
annotations:
tags: instant-app,javaee
iconClass: icon-jboss
description: |
Ticket Monster is a moderately complex application that demonstrates how
to build modern applications using JBoss web technologies

parameters:
- name: GIT_URI
value: git://github.com/kenthua/ticket-monster-ose
- name: MYSQL_DATABASE
value: monster
- name: MYSQL_USER
value: monster
- name: MYSQL_PASSWORD
from: '[a-zA-Z0-9]{8}'
generate: expression


objects:
- kind: ImageStream
apiVersion: v1
metadata:
name: monster

-kind: BuildConfig
apiVersion: v1
metadata:
name: monster
spec:
triggers:
- type: Generic
generic:
secret: secret
- type: ImageChange
- type: ConfigChange
strategy:
type: Source
sourceStrategy:
from:
kind: ImageStreamTag
name: jboss-eap64-openshift:latest
namespace: openshift
source:
type: Git
git:
uri: ${GIT_URI}
ref: master
output:
to:
kind: ImageStreamTag
name: monster:latest

-kind: DeploymentConfig
apiVersion: v1
metadata:
name: monster
spec:
replicas: 1
selector:
deploymentConfig: monster
template:
metadata:
labels:
deploymentConfig: monster
name: monster
spec:
containers:
- name: monster
image: monster
ports:
- name: http
containerPort: 8080
- name: jolokia
containerPort: 8778
- name: debug
containerPort: 8787
readinessProbe:
exec:
command:
- /bin/bash
- -c
- /opt/eap/bin/readinessProbe.sh
env:
- name: DB_SERVICE_PREFIX_MAPPING
value: monster-mysql=DB
- name: TX_DATABASE_PREFIX_MAPPING
value: monster-mysql=DB
- name: DB_JNDI
value: java:jboss/datasources/MySQLDS
- name: DB_DATABASE
value: ${MYSQL_DATABASE}
- name: DB_USERNAME
value: ${MYSQL_USER}
- name: DB_PASSWORD
value: ${MYSQL_PASSWORD}
- name: JAVA_OPTS
value: "-Xmx512m -XX:MaxPermSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.logmanager -Djava.awt.headless=true -Djboss.modules.policy-permissions=true"
- name: DEBUG
value: "true"
triggers:
- type: ImageChange
imageChangeParams:
automatic: true
containerNames:
- monster
from:
kind: ImageStream
name: monster

-kind: DeploymentConfig
apiVersion: v1
metadata:
name: monster-mysql
spec:
triggers:
- type: ImageChange
imageChangeParams:
automatic: true
containerNames:
- monster-mysql
from:
kind: ImageStreamTag
name: mysql:latest
namespace: openshift
replicas: 1
selector:
deploymentConfig: monster-mysql
template:
metadata:
labels:
deploymentConfig: monster-mysql
name: monster-mysql
spec:
containers:
- name: monster-mysql
image: mysql
ports:
- containerPort: 3306
env:
- name: MYSQL_USER
value: ${MYSQL_USER}
- name: MYSQL_PASSWORD
value: ${MYSQL_PASSWORD}
- name: MYSQL_DATABASE
value: ${MYSQL_DATABASE}

-kind: Service
apiVersion: v1
metadata:
name: monster
spec:
ports:
- name: http
port: 8080
selector:
deploymentConfig: monster

-kind: Service
apiVersion: v1
metadata:
name: monster-mysql
spec:
ports:
- port: 3306
selector:
deploymentConfig: monster-mysql

-kind: Route
apiVersion: v1
metadata:
name: monster
spec:
to:
name: monster

# oc create -n openshift -f monster.yaml

创建ticket monster项目的生产环境模板

当开发环境中用monster:prod触发了image stream,下面这个触发器将会把image stream部署到生产环境中。
# vi monster-prod.yaml
kind: Template
apiVersion: v1
metadata:
name: monster-prod
annotations:
tags: instant-app,javaee
iconClass: icon-jboss
description: |
Ticket Monster is a moderately complex application that demonstrates how
to build modern applications using JBoss web technologies. This template
is for "production deployments" of Ticket Monster.

parameters:
- name: MYSQL_DATABASE
value: monster
- name: MYSQL_USER
value: monster
- name: MYSQL_PASSWORD
from: '[a-zA-Z0-9]{8}'
generate: expression

objects:
- kind: DeploymentConfig
apiVersion: v1
metadata:
name: monster
spec:
replicas: 3
selector:
deploymentConfig: monster
template:
metadata:
labels:
deploymentConfig: monster
name: monster
spec:
containers:
- name: monster
image: monster
ports:
- name: http
containerPort: 8080
- name: jolokia
containerPort: 8778
readinessProbe:
exec:
command:
- /bin/bash
- -c
- /opt/eap/bin/readinessProbe.sh
env:
- name: DB_SERVICE_PREFIX_MAPPING
value: monster-mysql=DB
- name: TX_DATABASE_PREFIX_MAPPING
value: monster-mysql=DB
- name: DB_JNDI
value: java:jboss/datasources/MySQLDS
- name: DB_DATABASE
value: ${MYSQL_DATABASE}
- name: DB_USERNAME
value: ${MYSQL_USER}
- name: DB_PASSWORD
value: ${MYSQL_PASSWORD}
- name: JAVA_OPTS
value: "-Xmx512m -XX:MaxPermSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.logmanager -Djava.awt.headless=true -Djboss.modules.policy-permissions=true"
triggers:
- type: ImageChange
imageChangeParams:
automatic: true
containerNames:
- monster
from:
kind: ImageStreamTag
name: monster:prod
namespace: ticket-monster-dev

-kind: DeploymentConfig
apiVersion: v1
metadata:
name: monster-mysql
spec:
triggers:
- type: ImageChange
imageChangeParams:
automatic: true
containerNames:
- monster-mysql
from:
kind: ImageStreamTag
name: mysql:latest
namespace: openshift
replicas: 1
selector:
deploymentConfig: monster-mysql
template:
metadata:
labels:
deploymentConfig: monster-mysql
name: monster-mysql
spec:
containers:
- name: monster-mysql
image: mysql
ports:
- containerPort: 3306
env:
- name: MYSQL_USER
value: ${MYSQL_USER}
- name: MYSQL_PASSWORD
value: ${MYSQL_PASSWORD}
- name: MYSQL_DATABASE
value: ${MYSQL_DATABASE}

-kind: Service
apiVersion: v1
metadata:
name: monster
spec:
ports:
- name: http
port: 8080
selector:
deploymentConfig: monster

-kind: Service
apiVersion: v1
metadata:
name: monster-mysql
spec:
ports:
- port: 3306
selector:
deploymentConfig: monster-mysql

-kind: Route
apiVersion: v1
metadata:
name: monster
spec:
to:
name: monster

# oc create -n openshift -f monster-prod.yaml

部署ticket-monster开发环境

用WebUI你可以选择monster的模板。
2.jpg

部署ticket-monster到生产环境

部署ticket-monster生产环境模板(monster-prod)。
3.jpg

你可以看到在开发环境中程序被创建,并且可以通过 http://monster-ticket-monster-dev.apps.lab访问到它。
4.jpg

你可以看到在生产环境中,虽然数据库已经运行起来了,并且创建了一个服务,但是ticket monster应用却没有启动。这是因为我们创建的生产环境模板会从开发环境中自动拉取这个带有特定名字或者标签images。在开发环境中只部署的规模是1,但是生产环境中部署的规模是4。

部署应用程序的开发版本到生产环境

拉取images stream的spec。
# oc get is monster -o yaml
apiVersion: v1
kind: ImageStream
metadata:
annotations:
openshift.io/image.dockerRepositoryCheck: 2016-08-09T13:37:47Z
creationTimestamp: 2016-08-09T13:14:53Z
generation: 7
name: monster
namespace: ticket-monster-dev
resourceVersion: "107170"
selfLink: /oapi/v1/namespaces/ticket-monster-dev/imagestreams/monster
uid: 42740a3d-5e33-11e6-aa8d-001a4ae42e01
spec:
tags:
- annotations: null
from:
kind: ImageStreamImage
name: monster@sha256:3a48a056a58f50764953ba856d90eba73dd0dfdee10b8cb6837b0fd9461da7f9
generation: 7
importPolicy: {}
name: prod
status:
dockerImageRepository: 172.30.139.50:5000/ticket-monster-dev/monster
tags:
- items:
- created: 2016-08-09T13:26:04Z
dockerImageReference: 172.30.139.50:5000/ticket-monster-dev/monster@sha256:3a48a056a58f50764953ba856d90eba73dd0dfdee10b8cb6837b0fd9461da7f9
generation: 1
image: sha256:3a48a056a58f50764953ba856d90eba73dd0dfdee10b8cb6837b0fd9461da7f9
tag: latest 

一旦你有了一个拉取image stream的说明(就像上面的那个),标记这个images stream为monster:prod。
# oc tag monster@sha256:3a48a056a58f50764953ba856d90eba73dd0dfdee10b8cb6837b0fd9461da7f9 monster:prod 

你可以效验一下这个image stream是否已经生效
# oc get is
NAME DOCKER REPO TAGS UPDATED
monster 172.30.139.50:5000/ticket-monster-dev/monster prod,latest 2 minutes ago

只要这个images stream在ticket-monster-dev中被标记成monster:prod,他就会被部署到生产环境中,并且按照上面的描述把部署规模设置成4。
5.jpg

场景 3:用Jenkins实现AB(应用的名字叫AB)部署

在这个场景中我们将看到如何用Jenkins实现一个简单应用AB的部署。这个场景中用到了在场景1和2中学到的东西。在这个场景中我们会创建一个稍稍有点复杂的三个环境:开发、集成和生产。我们会给应用程序创建2个版本,v1和v2在开发环境里。用Jenkins部属V2版本的应用到集成生产环境。最终我们将在生产环境中展示这个应用从V2版本回滚到V1版本。我很感谢我的同事Torben Jaeger,下面很多内容是他编写的。

创建项目

# oc new-project dev && \
oc new-project int && \
oc new-project prod

设置拉取(pull)的权限

允许int项目从dev项目中拉取images,并且允许prod项目从dev项目和int项目中拉取images。
# oc policy add-role-to-group system:image-puller system:serviceaccounts:int -n dev

# oc policy add-role-to-group system:image-puller system:serviceaccounts:prod -n int

# oc policy add-role-to-group system:image-puller system:serviceaccounts:prod -n dev

在开发项目中设置Jenkins

在WebUI中进入dev项目,选择添加jenkins-ephemeral。
6.jpg

克隆Github的repository

# git clone https://github.com/ktenzer/openshift-demo.git

更新Jenkins的认证tokens

从OpenShift命令获得认证token,这个token可以让Jenkins访问OpenShift环境。你需要更新它
# oc login -u admin
# oc whoami -t
DMzhKyEN87DZiDYV6i1d8L8NL2e6gFVFPpT5FnozKtU

更新下面的Jenkins任务,并且用上面的token替换其中的authToken和destinationAuthToken
# ls jenkins-jobs/
promote-int.xml promote-prod.xml rel-v2.xml rollback-prod.xml

配置三个环境

用你的token更新三个模板并创建三个环境。
# cd openshift-demo

# oc create -f template.json -n dev

# oc create -f acceptance.template.json -n int

# oc create -f production.template.json -n prod

在开发项目中部署nodejs 的hello-world应用

把环境
# oc new-app -f template.json -n dev

这个模板创建了两个版本的应用v1-ab和v2-ab。
7.jpg

在开发项目中测试应用程序

通过http或者curl工具去连接v1-ab因该可以打印出“hello World!”。如果连接v2-ab可以打印出”hello World,welcome to Frankfurt!“。

把V1从开发项目部署到集成项目

把v1的image打一个标记(tag)部署到集成项目。当镜像acceptance:lastest被更新时触发器会部署它到集成项目里。你会看到v1版本的pod被启动并运行起来。
oc tag v1:latest v1:1.0 -n dev
oc tag dev/v1:1.0 acceptance:v1 -n int
oc tag acceptance:v1 acceptance:latest -n int

把v1从集成项目部署到生产项目

把v1打一个标记(tag)部署到生产项目。当镜像production:latest被更新时生产环境中的触发器会部署它到生产项目中。
oc tag int/acceptance:v1 production:v1 -n prod
oc tag production:v1 production:latest -n prod

注意应用被署了4个pod并且代码版本是v1。再次说明一下,这里所有的定义都来自之前的模板。
8.jpg

配置Jenkins

你已经知道了如何手动的,用打标记和设置触发器的方法去部署一个应用程序。下面我们来一点复杂的,通过在jenkins里安装OpenShift的插件去从新编排一下刚才的任务。这次我们将部署v2版本到集成项目和开发项目

这里可以得到jenkins中的4个job:第一个的作用是创建v2版的应用程序,第二个用来从开发部署集成,第三个实现了从集成到生产,第四个可以在生产项目中把应用从v2回滚到v1。
# curl -k -u admin:password -XPOST -d @jenkins-jobs/rel-v2.xml 'https://jenkins-dev.apps.lab/createItem?name=rel-v2' -H "Content-Type: application/xml"
# curl -k -u admin:password -XPOST -d @jenkins-jobs/promote-int.xml 'https://jenkins-dev.apps.lab/createItem?name=promote-int' -H "Content-Type: application/xml"
# curl -k -u admin:password -XPOST -d @jenkins-jobs/promote-prod.xml 'https://jenkins-dev.apps.lab/createItem?name=promote-prod' -H "Content-Type: application/xml"
# curl -k -u admin:password -XPOST -d @jenkins-jobs/rollback-prod.xml 'https://jenkins-dev.apps.lab/createItem?name=rollback-prod' -H "Content-Type: application/xml"

创建4个job,并且登陆到Jenkins,用户名和密码是:admin和password。
9.jpg

这里可以修改一点v2的代码并且开始build

为了让这个代码工作,你需要在Github上fork一个nodejs-ex项目到你自己的账户下,并且更新template.json文件,然后用这个文件从新部署一下开发环境。
"source": {
"type": "Git",
"git": {
"uri": "https://github.com/ktenzer/nodejs-ex.git",
"ref": "master"
}, 

# git clone https://github.com/ktenzer/nodejs-ex

# cd nodejs-ex

Checkout到v2的branch并且commit

# git checkout v2
# vi index.html
Hello World, welcome to Munich!

提交修改

# git commit -a -m "updated to munich"
# git push origin v2

在Jenkins里运行rhel-v2这个build。
10.jpg

如果你克隆了nodejs-ex的repository,这时你可以通过URL看到你的修改已经生效了。

把v2从开发项目部署到集成项目

在Jenkins里运行promote-int这个build,你会看到有一个v2版本的pod在v1版本后启动。
11.jpg

从集成环境部署v2到生产环境

我们在这里深入的看一下在hood的后面实际发生了些什么。

可以用curl观察到应用程序是如何从v1切换到v2的。
# for i in {1..10000};do curl prod-ab.apps.lab; sleep 1;done

在Jenkins里运行promote-prod这个build。
12.jpg

部署已经开始了,v2版本在v1版本之后启动。此时应用的服务仍然还是v1。
13.jpg

两个v2版本的pod运行起来了,探针(readiness)会去检查这两个pod,以确保v2应用提供的服务可用。然后把v1的pod逐个关闭。
14.jpg

当v2版本的4个pod都运行起来的时候,v1版本的pod的数量是0。在这个过程中我们可以看到有一段时间,v1和v2是同时响应请求的。
15.jpg

现在只有v2版本的pod在运行了,同时AB的部署也完成了。
16.jpg

回滚

现在假设我们不满意v2版本,想回到旧的v1版本去。

在Jenkins里运行rollback-prod这个build。
17.jpg

现在我们观察到了同样的事情,只不过是v2切换到了v1。
18.jpg

可以用下面的命令把集成环境中的v2回滚到v1。
# oc tag dev/v1:latest acceptance:latest -n int

总结

在这片文章中我们学到了如何在OpenShift中部署一个简单的应用程序并且和Jenkins集成去管理应用的发布。OpenShift有很多的功能值得你使用它,我们紧紧是用了其中的一小部分。使用Jenkins你可以创建一个非常复杂的build pipelines,它能让你不仅能够控制而且可以看到应用部署的过程。我们看到了一种典型的部署方式就是部署应用AB,但还有很多其他的方式比如blue-green或者canary。在以后的文章中我会演示OpenShift中其他的部署模式。请分享你的经验和成果。我希望这篇文章能够帮到你。

Happy OpenShifting!

原文链接:OpenShift v3: Basic Release Deployment Scenarios(翻译:王晓轩,校对:许小平)

2 个评论

岂止一个吊字了得, 辛苦了。
有实践才能翻译的这么清晰,感谢分享

要回复文章请先登录注册