这段时间学习了一下 git jenkins docker 最近也在看 Kubernetes 感觉写得很赞 也是对自己对于K8S 有了进一步得理解 感谢 倪 大神得Blog 也希望看到这篇Blog 得人 有点帮助 ‘“Kubernetes初体验” 转自 http://time-track.cn/kubernetes-trial.html
Kubernetes Cluster
Kubernetes is a production-grade, open-source platform that orchestrates the placement (scheduling) and execution of application containers within and across computer clusters.
Kubernetes将底层的计算资源连接在一起对外体现为一个计算集群,并将资源高度抽象化。部署应用时Kubernetes会以更高效的方式自动的将应用分发到集群内的机器上面,并调度运行。几个Kubernetes集群包含两种类型的资源:
Master节点:协调控制整个集群。
Nodes节点:运行应用的工作节点。
如下图:
Masters manage the cluster and the nodes are used to host the running applications.
Master负责管理整个集群,协调集群内的所有行为。比如调度应用,监控应用的状态等。
Node节点负责运行应用,一般是一台物理机或者虚机。每个Node节点上面都有一个Kubelet,它是一个代理程序,用来管理该节点以及和Master节点通信。除此以外,Node节点上还会有一些管理容器的工具,比如Docker或者rkt等。生产环境中一个Kubernetes集群至少应该包含三个Nodes节点。
当部署应用的时候,我们通知Master节点启动应用容器。然后Master会调度这些应用将它们运行在Node节点上面。Node节点和Master节点通过Master节点暴露的Kubernetes API通信。当然我们也可以直接通过这些API和集群交互。
Kubernetes提供了一个轻量级的Minikube应用,利用它我们可以很容器的创建一个只包含一个Node节点的Kubernetes Cluster用于日常的开发测试。
安装Minikube
Minikube的Github:https://github.com/kubernetes/minikube
Minikube提供OSX、Linux、Windows版本,本文测试环境为OSX,且以当时最新的版本为例。实际操作时,尽量到官网查看最新的版本及安装命令。
MacOS安装:
curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.15.0/minikube-darwin-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
Linux安装:
curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.15.0/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
Minikube要正常使用,还必须安装kubectl,并且放在PATH里面。kubectl是一个通过Kubernetes API和Kubernetes集群交互的命令行工具。安装方法如下:
MACOS安装:
wget https://storage.googleapis.com/kubernetes-release/release/v1.5.1/bin/darwin/amd64/kubectl
chmod +x kubectl
mv kubectl /usr/local/bin/kubectl
Linux安装:
wget https://storage.googleapis.com/kubernetes-release/release/v1.5.1/bin/linux/amd64/kubectl
chmod +x kubectl
mv kubectl /usr/local/bin/kubectl
因为Minikube使用Docker Machine管理Kubernetes的虚机,所以我们还需要安装一些虚机的driver,这里推荐使用Virtualbox,因为安装简单(从官网下载安装即可),而且后面可以简单的登录到VM里面去进行一些操作。其它driver的安装请参考官方文档:https://github.com/kubernetes/minikube/blob/master/DRIVERS.md。
安装完以后,我们可以使用一下命令进行一些信息查看:
minikube version # 查看版本
minikube start --vm-driver=virtualbox # 启动Kubernetes Cluster,这里使用的driver是Virtualbox
kubectl version # 查看版本
kubectl cluster-info # 查看集群信息
kubectl get nodes # 查看当前可用的Node,使用Minikube创建的cluster只有一个Node节点
至此,我们已经用Minikube创建了一个Kubernetes Cluster,下面我们在这个集群上面部署一个应用。
Only For Chinese
在进行下一节部署具体应用前我们先要做一件事情。Kubernetes在部署容器应用的时候会先拉一个pause镜像,这个是一个基础容器,主要是负责网络部分的功能的,具体这里不展开讨论。最关键的是Kubernetes里面镜像默认都是从Google的镜像仓库拉的(就跟docker默认从docker hub拉的一样),但是因为GFW的原因,中国用户是访问不了Google的镜像仓库gcr.io的(如果你可以ping通,那恭喜你)。庆幸的是这个镜像被传到了docker hub上面,虽然中国用户访问后者也非常艰难,但通过一些加速器之类的还是可以pull下来的。如果没有VPN等*的工具的话,请先做如下操作:
minikube ssh # 登录到我们的Kubernetes VM里面去
docker pull hub.c.163.com/allan1991/pause-amd64:3.0
docker tag hub.c.163.com/allan1991/pause-amd64:3.0 gcr.io/google_containers/pause-amd64:3.0
我们先从其他镜像仓库(这里我使用的是HNA CloudOS容器云平台提供的镜像仓库)下载Kubernetes需要的基础网络容器pause,Mac OSX上面kubectl 1.5.1版本需要的是pause-amd64:3.0
,然后我将其打成gcr.io/google_containers/pause-amd64:3.0
。这样Kubernetes VM就不会从gcr.io拉镜像了,而是会直接使用本地的镜像。
关于这个问题,Kubernetes上面还专门有个issue讨论,有兴趣的可以看看:https://github.com/kubernetes/kubernetes/issues/6888。
OK,接着我们可以进行下一步了。
Deploy an App
在Kubernetes Cluster上面部署应用,我们需要先创建一个Kubernetes Deployment。这个Deployment负责创建和更新我们的应用实例。当这个Deployment创建之后,Kubernetes master就会将这个Deployment创建出来的应用实例部署到集群内某个Node节点上。而且自应用实例创建后,Deployment controller还会持续监控应用,直到应用被删除或者部署应用的Node节点不存在。
A Deployment is responsible for creating and updating instances of your application
这里我们依旧使用kubectl来创建Deployment,创建的时候需要制定容器镜像以及我们要启动的个数(replicas),当然这些信息后面可以再更新。这里我用Go写了一个简单的Webserver,返回“Hello World”,监听端口是8090.我们就来启动这个应用(镜像地址:registry.hnaresearch.com/public/hello-world:v1.0 备用镜像地址:hub.c.163.com/allan1991/hello-world:v1.0)
kubectl run helloworld --image=registry.hnaresearch.com/public/hello-world:v1.0 --port=8090
这条命令执行后master寻找一个合适的node来部署我们的应用实例(我们只有一个node)。我们可以使用kubectl get deployment
来查看我们创建的Deployment:
➜ ~ kubectl get deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
helloworld 1 1 1 1 53m
默认应用部署好之后是只在Kubernetes Cluster内部可见的,有多种方法可以让我们的应用暴露到外部,这里先介绍一种简单的:我们可以通过kubectl proxy
命令在我们的终端和Kubernetes Cluster直接创建一个代理。然后,打开一个新的终端,通过Pod名(Pod后面会有讲到,可以通过kubectl get pod
查看Pod名字)就可以访问了:
➜ ~ kubectl proxy
Starting to serve on 127.0.0.1:8001
# 打开新的终端
➜ ~ kubectl get pod
NAME READY STATUS RESTARTS AGE
helloworld-2080166056-4q88d 1/1 Running 0 1h
➜ ~ curl http://localhost:8001/api/v1/proxy/namespaces/default/pods/helloworld-2080166056-4q88d/
Hello world !
OK,我们的第一个应用已经部署成功了。如果你的应用有问题(比如kubectl get deployment执行后看到AVAILABL是0),可以通过kubectl describe pod pod名字
来查看信息(最后面的Events部分是该Pod的信息),看哪里出错了。
好吧,这里我们已经提到了Pod,它是Kubernetes中一个非常重要的概念,也是区别于其他编排系统的一个设计,这里我们简单介绍一下。
Pods和Nodes
其实我们创建了Deployment之后,它并不是直接创建了容器实例,而是先在Node上面创建了Pod,然后再在Pod里面创建容器。那Pod到底是什么?Pod是Kubernetes里面抽象出来的一个概念,它是能够被创建、调度和管理的最小单元;每个Pod都有一个独立的IP;一个Pod由若干个容器构成。一个Pod之内的容器共享Pod的所有资源,这些资源主要包括:共享存储(以Volumes的形式)、共享网络、共享端口等。Kubernetes虽然也是一个容器编排系统,但不同于其他系统,它的最小操作单元不是单个容器,而是Pod。这个特性给Kubernetes带来了很多优势,比如最显而易见的是同一个Pod内的容器可以非常方便的互相访问(通过localhost就可以访问)和共享数据。
A Pod is a group of one or more application containers (such as Docker or rkt) and includes shared storage (volumes), IP address and information about how to run them.
Containers should only be scheduled together in a single Pod if they are tightly coupled and need to share resources such as disk.
Services和Labels
上一步我们已经创建了一个应用,并且通过proxy实现了集群外部可以访问,但这种Proxy的方式是不太适用于实际生产环境的。本节我们再介绍一个Kubernetes里面另外一个非常重要的概念———Service。Service是Kubernetes里面抽象出来的一层,它定义了由多个Pods组成的逻辑组(logical set),可以对组内的Pod做一些事情:
对外暴露流量
做负载均衡(load balancing)
服务发现(service-discovery)。
而且每个Service都有一个集群内唯一的私有IP和对外的端口,用于接收流量。如果我们想将一个Service暴露到集群外,有两种方法:
LoadBalancer - 提供一个公网的IP
NodePort - 使用NAT将Service的端口暴露出去。Minikube只支持这种方式。
A Kubernetes Service is an abstraction layer which defines a logical set of Pods and enables external traffic exposure, load balancing and service discovery for those Pods.
我们再来介绍一下Kubernetes里面的第三个比较重要的概念——Label。Service就是靠Label选择器(Label Selectors)来匹配组内的Pod的,而且很多命令都可以操作Label。Label是绑定在对象上(比如Pod)的键值对,主要用来把一些相关的对象组织在一起,并且对于用户来说label是有含义的,比如:
Production environment (production, test, dev)
Application version (beta, v1.3)
Type of service/server (frontend, backend, database)
当然,Label是随时可以更改的。
Labels are key/value pairs that are attached to objects
接着上的例子,我们使用Service的方式将我们之前部署的helloworld应用暴露到集群外部。因为Minikube只支持NodePort方式,所以这里我们使用NodePort方式。使用kubectl get service
可以查看目前已有的service,Minikube默认创建了一个kubernetes Service。我们使用expose
命令再创建一个Service:
# 查看已有Service
➜ ~ kubectl get service
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-nginx 10.0.0.163 <pending> 80:31943/TCP 1d
helloworld 10.0.0.114 <nodes> 8090:30350/TCP 1h
kubernetes 10.0.0.1 <none> 443/TCP 1d
# 创建新的Service
➜ ~ kubectl expose deployment/helloworld --type="NodePort" --port 8090
service "helloworld" exposed
# 查看某个Service的详细信息
➜ ~ kubectl describe service/helloworld
Name: helloworld
Namespace: default
Labels: run=helloworld
Selector: run=helloworld
Type: NodePort
IP: 10.0.0.114
Port: <unset> 8090/TCP
NodePort: <unset> 30350/TCP
Endpoints: 172.17.0.3:8090
Session Affinity: None
No events.
这样内部应用helloworld的8090端口映射到了外部的30350端口。此时我们就可以通过外部访问里面的应用了:
➜ ~ curl 192.168.99.100:30350
Hello world !
这里的IP是minikube的Docker host的IP,可以通过minikube docker-env
命令查看。再看Label的例子,创建Pod的时候默认会生产一个Label和Label Selector,我们可以使用kubectl label
命令创建新的Label。
➜ ~ kubectl describe pod helloworld-2080166056-4q88d
Name: helloworld-2080166056-4q88d
Namespace: default
Node: minikube/192.168.99.100
Start Time: Tue, 10 Jan 2017 16:36:22 +0800
Labels: pod-template-hash=2080166056 # 默认的Label
run=helloworld
Status: Running
IP: 172.17.0.3
Controllers: ReplicaSet/helloworld-2080166056
Containers:
helloworld:
Container ID: docker://a45e88e7cb9fdb193a2ed62539918e293aa381d5c8fedb57ed78e54df9ea502a
Image: registry.hnaresearch.com/public/hello-world:v1.0
Image ID: docker://sha256:b3737bd8b3af68017bdf932671212be7211f4cdc47cd63b2300fbd71057ecf83
Port: 8090/TCP
State: Running
Started: Tue, 10 Jan 2017 16:36:32 +0800
Ready: True
Restart Count: 0
Volume Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-zfl0m (ro)
Environment Variables: <none>
Conditions:
Type Status
Initialized True
Ready True
PodScheduled True
Volumes:
default-token-zfl0m:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-zfl0m
QoS Class: BestEffort
Tolerations: <none>
No events.
# 新增一个Label
➜ ~ kubectl label pod helloworld-2080166056-4q88d app=v1
pod "helloworld-2080166056-4q88d" labeled
➜ ~ kubectl describe pod helloworld-2080166056-4q88d
Name: helloworld-2080166056-4q88d
Namespace: default
Node: minikube/192.168.99.100
Start Time: Tue, 10 Jan 2017 16:36:22 +0800
Labels: app=v1 # 新增的Label
pod-template-hash=2080166056
run=helloworld
Status: Running
IP: 172.17.0.3
Controllers: ReplicaSet/helloworld-2080166056
Containers:
helloworld:
Container ID: docker://a45e88e7cb9fdb193a2ed62539918e293aa381d5c8fedb57ed78e54df9ea502a
Image: registry.hnaresearch.com/public/hello-world:v1.0
Image ID: docker://sha256:b3737bd8b3af68017bdf932671212be7211f4cdc47cd63b2300fbd71057ecf83
Port: 8090/TCP
State: Running
Started: Tue, 10 Jan 2017 16:36:32 +0800
Ready: True
Restart Count: 0
Volume Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-zfl0m (ro)
Environment Variables: <none>
Conditions:
Type Status
Initialized True
Ready True
PodScheduled True
Volumes:
default-token-zfl0m:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-zfl0m
QoS Class: BestEffort
Tolerations: <none>
No events.
# 使用Label的例子
➜ ~ kubectl get service -l run=helloworld
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
helloworld 10.0.0.114 <nodes> 8090:30350/TCP 5m
➜ ~ kubectl get pod -l app=v1
NAME READY STATUS RESTARTS AGE
helloworld-2080166056-4q88d 1/1 Running 0 1d
删掉一个Service使用kubectl delete service命令:
➜ ~ kubectl delete service helloworld
删除Service后,我们就没法通过外部访问到内部应用了,但是Pod内的应用依旧是在正常运行的。
Scale an App
PS:这个Scale不知道咋翻译才好,就用scale吧。
Scaling is accomplished by changing the number of replicas in a Deployment.
刚才我们部署了一个应用,并且增加了Service。但是这个应用只运行在一个Pod上面。随着流量的增加,我们可能需要增加我们应用的规模来满足用户的需求。Kubernetes的Scale功能就可以实现这个需求。
You can create from the start a Deployment with multiple instances using the --replicas parameter for the kubectl run command
扩大应用的规模时,Kubernetes将会在Nodes上面使用可用的资源来创建新的Pod,并运行新增加的应用,缩小规模时做相反的操作。Kubernetes也支持自动规模化Pod。当然我们也可以将应用的数量变为0,这样就会终止所有部署该应用的Pods。应用数量增加后,Service内的负载均衡就会变得非常有用了,为了表现出这个特性,我修改了一下程序,除了打印“Hello world”以外,还会打印主机名。我们先看一下现有的一些输出字段的含义:
➜ ~ kubectl get deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
helloworld 1 1 1 1 2m
可以看到,现在我们只有一个Pod,
DESIRED字段表示我们配置的replicas的个数,即实例的个数。
CURRENT字段表示目前处于running状态的replicas的个数。
UP-TO-DATE字段表示表示和预先配置的期望状态相符的replicas的个数。
AVAILABLE字段表示目前实际对用户可用的replicas的个数。
下面我们使用kubectl scale
命令将启动4个复制品,语法规则是kubectl scale deployment-type name replicas-number
:‘
➜ ~ kubectl scale deployment/helloworld --replicas=4
deployment "helloworld" scaled
# 查看应用实例个数
➜ ~ kubectl get deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
helloworld 4 4 4 4 9m
# 查看Pod个数
➜ ~ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE
helloworld-2080166056-fn03c 1/1 Running 0 9m 172.17.0.2 minikube
helloworld-2080166056-jlwz1 1/1 Running 0 32s 172.17.0.4 minikube
helloworld-2080166056-mmpn4 1/1 Running 0 32s 172.17.0.3 minikube
helloworld-2080166056-wh696 1/1 Running 0 32s 172.17.0.5 minikube
可以看到,我们已经有4个应用实例了,而且Pod个数也变成4个了,每个都有自己的IP。当然,日志里面也有相关信息:
➜ ~ kubectl describe deployment/helloworld
Name: helloworld
Namespace: default
CreationTimestamp: Thu, 12 Jan 2017 13:37:47 +0800
Labels: run=helloworld
Selector: run=helloworld
Replicas: 4 updated | 4 total | 4 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 1 max unavailable, 1 max surge
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
OldReplicaSets: <none>
NewReplicaSet: helloworld-2080166056 (4/4 replicas created)
Events:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
11m 11m 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set helloworld-2080166056 to 1
2m 2m 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set helloworld-2080166056 to 4
然后我们再看下之前我们创建的Service信息:
➜ ~ kubectl describe service/helloworld
Name: helloworld
Namespace: default
Labels: run=helloworld
Selector: run=helloworld
Type: NodePort
IP: 10.0.0.170
Port: <unset> 8090/TCP
NodePort: <unset> 31030/TCP
Endpoints: 172.17.0.2:8090,172.17.0.3:8090,172.17.0.4:8090 + 1 more...
Session Affinity: None
No events.
可以看到Service的信息也已经更新了。让我们验证一下这个Service是有负载均衡的:
➜ ~ curl 192.168.99.100:31030
Hello world !
hostname:helloworld-2080166056-jlwz1
➜ ~ curl 192.168.99.100:31030
Hello world !
hostname:helloworld-2080166056-mmpn4
➜ ~ curl 192.168.99.100:31030
Hello world !
hostname:helloworld-2080166056-wh696
➜ ~ curl 192.168.99.100:31030
Hello world !
hostname:helloworld-2080166056-mmpn4
➜ ~ curl 192.168.99.100:31030
Hello world !
hostname:helloworld-2080166056-wh696
接着我们将实例缩减为2个:
➜ ~ kubectl scale deployment/helloworld --replicas=2
deployment "helloworld" scaled
➜ ~ kubectl get deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
helloworld 2 2 2 2 18m
➜ ~ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE
helloworld-2080166056-fn03c 1/1 Running 0 18m 172.17.0.2 minikube
helloworld-2080166056-wh696 1/1 Running 0 9m 172.17.0.5 minikube
Update an App
Kubernetes还提供了一个非常有用的特性——滚动更新(Rolling update),这个特性的好处就是我们不用停止服务就可以实现应用更新。默认更新的时候是一个Pod一个Pod更新的,所以整个过程服务不会中断。当然你也可以设置一次更新的Pod的百分比。而且更新过程中,Service只会将流量转发到可用的节点上面。更加重要的是,我们可以随时回退到旧版本。
Rolling updates allow Deployments' update to take place with zero downtime by incrementally updating Pods instances with new ones.
If a Deployment is exposed publicly, the Service will load-balance the traffic only to available Pods during the update.
OK,我们来实践一下。我们在原来程序的基础上,多输出一个v2作为新版本,使用set image
命令指定新版本镜像。
# 使用set image命令执行新版本镜像
➜ ~ kubectl set image deployments/helloworld helloworld=registry.hnaresearch.com/public/hello-world:v2.0
deployment "helloworld" image updated
➜ ~ kubectl get pod
NAME READY STATUS RESTARTS AGE
helloworld-2080166056-fn03c 1/1 Running 0 28m
helloworld-2161692841-8psgp 0/1 ContainerCreating 0 5s
helloworld-2161692841-cmzg0 0/1 ContainerCreating 0 5s
# 更新中
➜ ~ kubectl get pods
NAME READY STATUS RESTARTS AGE
helloworld-2080166056-fn03c 1/1 Running 0 28m
helloworld-2161692841-8psgp 0/1 ContainerCreating 0 20s
helloworld-2161692841-cmzg0 0/1 ContainerCreating 0 20s
# 已经更新成功
➜ ~ kubectl get pods
NAME READY STATUS RESTARTS AGE
helloworld-2161692841-8psgp 1/1 Running 0 2m
helloworld-2161692841-cmzg0 1/1 Running 0 2m
➜ ~ curl 192.168.99.100:31030
Hello world v2! # 输出以变为v2
hostname:helloworld-2161692841-cmzg0
然后我们继续更新到不存在的v3版本:
➜ ~ kubectl set image deployments/helloworld helloworld=registry.hnaresearch.com/public/hello-world:v3.0
deployment "helloworld" image updated
# 更新中
➜ ~ kubectl get pods
NAME READY STATUS RESTARTS AGE
helloworld-2161692841-cmzg0 1/1 Running 0 16m
helloworld-2243219626-7qhzh 0/1 ContainerCreating 0 4s
helloworld-2243219626-rfw64 0/1 ContainerCreating 0 4s
# 更新时,Service只会讲请求转发到可用节点
➜ ~ curl 192.168.99.100:31030
Hello world v2!
hostname:helloworld-2161692841-cmzg0
➜ ~ curl 192.168.99.100:31030
Hello world v2!
hostname:helloworld-2161692841-cmzg0
# 因为镜像不存在,所以更新失败了。但仍然有一个Pod是可用的
➜ ~ kubectl get pod
NAME READY STATUS RESTARTS AGE
helloworld-2161692841-cmzg0 1/1 Running 0 16m
helloworld-2243219626-7qhzh 0/1 ErrImagePull 0 21s
helloworld-2243219626-rfw64 0/1 ErrImagePull 0 21s
因为我们指定了一个不存在的镜像,所以更新失败了。现在我们使用kubectl rollout undo
命令回滚到之前v2的版本:
➜ ~ kubectl rollout undo deployment/helloworld
deployment "helloworld" rolled back
➜ ~ kubectl get pod
NAME READY STATUS RESTARTS AGE
helloworld-2161692841-cmzg0 1/1 Running 0 19m
helloworld-2161692841-mw9g2 1/1 Running 0 5s
➜ ~ curl 192.168.99.100:31030
Hello world v2!
hostname:helloworld-2161692841-cmzg0
➜ ~ curl 192.168.99.100:31030
Hello world v2!
hostname:helloworld-2161692841-mw9g2
结束语
Kubernetes是Google根据他们多年的生产经验以及已有系统Brog等开发的一套全新系统,虽然目前我还只是个初学者,但是能够感觉到Kubernetes在一些结构设计方面相比于其他容器编排系统有着独特的见解。至于Kubernetes是什么,官方也有比较详细的说明:https://kubernetes.io/docs/whatisk8s。后面我抽空会将这篇文章翻译一下,以加深理解。
哦,对,Kubernetes一词源于希腊语,是舵手(helmsman)、船员(pilot)的意思。因为首字母和最后一个字母中间有8个字母,所以又简称K8s。