早在 Docker 正式发布几个月的时候,LeanCloud 就开始在生产环境大规模使用 Docker,在过去几年里 Docker 的技术栈支撑了我们主要的后端架构。这是一篇写给程序员的 Docker 和 Kubernetes 教程,目的是让熟悉技术的读者在尽可能短的时间内对 Docker 和 Kubernetes 有基本的了解,并通过实际部署、升级、回滚一个服务体验容器化生产环境的原理和好处。本文假设读者都是开发者,并熟悉 Mac/Linux 环境,所以就不介绍基础的技术概念了。命令行环境以 Mac 示例,在 Linux 下只要根据自己使用的发行版和包管理工具做调整即可。

Docker 速成

首先快速地介绍一下 Docker:作为示例,我们在本地启动 Docker 的守护进程,并在一个容器里运行简单的 HTTP 服务。先完成安装:

$ brew cask install docker

上面的命令会从 Homebrew 安装 Docker for Mac,它包含 Docker 的后台进程和命令行工具。Docker 的后台进程以一个 Mac App 的形式安装在 /Applications 里,需要手动启动。启动 Docker 应用后,可以在 Terminal 里确认一下命令行工具的版本:

$ docker --version
Docker version 18.03.1-ce, build 9ee9f40

上面显示的 Docker 版本可能和我的不一样,但只要不是太老就好。我们建一个单独的目录来存放示例所需的文件。为了尽量简化例子,我们要部署的服务是用 Nginx 来 serve 一个简单的 HTML 文件 html/index.html。

$ mkdir docker-demo
$ cd docker-demo
$ mkdir html
$ echo '<h1>Hello Docker!</h1>' > html/index.html

接下来在当前目录创建一个叫 Dockerfile 的新文件,包含下面的内容:

FROM nginx
COPY html/* /usr/share/nginx/html

每个 Dockerfile 都以 FROM ... 开头。 FROM nginx 的意思是以 Nginx 官方提供的镜像为基础来构建我们的镜像。在构建时,Docker 会从 Docker Hub 查找和下载需要的镜像。Docker Hub 对于 Docker 镜像的作用就像 GitHub 对于代码的作用一样,它是一个托管和共享镜像的服务。使用过和构建的镜像都会被缓存在本地。第二行把我们的静态文件复制到镜像的 /usr/share/nginx/html 目录下。也就是 Nginx 寻找静态文件的目录。Dockerfile 包含构建镜像的指令,更详细的信息可以参考这里。

然后就可以构建镜像了:

$ docker build -t docker-demo:0.1 .

请确保你按照上面的步骤为这个实验新建了目录,并且在这个目录中运行 docker build。如果你在其它有很多文件的目录(比如你的用户目录或者 /tmp)运行,Docker 会把当前目录的所有文件作为上下文发送给负责构建的后台进程。

这行命令中的名称 docker-demo 可以理解为这个镜像对应的应用名或服务名,0.1 是标签。Docker 通过名称和标签的组合来标识镜像。可以用下面的命令来看到刚刚创建的镜像:

$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-demo 0.1 efb8ca048d5a 5 minutes ago 109MB

下面我们把这个镜像运行起来。Nginx 默认监听在 80 端口,所以我们把宿主机的 8080 端口映射到容器的 80 端口:

$ docker run --name docker-demo -d -p 8080:80 docker-demo:0.1

用下面的命令可以看到正在运行中的容器:

$ docker container ps
CONTAINER ID IMAGE ... PORTS NAMES
c495a7ccf1c7 docker-demo:0.1 ... 0.0.0.0:8080->80/tcp docker-demo

这时如果你用浏览器访问 http://localhost:8080,就能看到我们刚才创建的「Hello Docker!」页面。

在现实的生产环境中 Docker 本身是一个相对底层的容器引擎,在有很多服务器的集群中,不太可能以上面的方式来管理任务和资源。所以我们需要 Kubernetes 这样的系统来进行任务的编排和调度。在进入下一步前,别忘了把实验用的容器清理掉:

$ docker container stop docker-demo
$ docker container rm docker-demo

安装 Kubernetes

介绍完 Docker,终于可以开始试试 Kubernetes 了。我们需要安装三样东西:Kubernetes 的命令行客户端 kubctl、一个可以在本地跑起来的 Kubernetes 环境 Minikube、以及给 Minikube 使用的虚拟化引擎 xhyve。

$ brew install kubectl
$ brew cask install minikube
$ brew install docker-machine-driver-xhyve

Minikube 默认的虚拟化引擎是 VirtualBox,而 xhyve 是一个更轻量、性能更好的替代。它需要以 root 权限运行,所以安装完要把所有者改为 root:wheel,并把 setuid 权限打开:

$ sudo chown root:wheel /usr/local/opt/docker-machine-driver-xhyve/bin/docker-machine-driver-xhyve
$ sudo chmod u+s /usr/local/opt/docker-machine-driver-xhyve/bin/docker-machine-driver-xhyve

然后就可以启动 Minikube 了:

$ minikube start --vm-driver xhyve

你多半会看到一个警告说 xhyve 会在未来的版本被 hyperkit 替代,推荐使用 hyperkit。不过在我写这个教程的时候 docker-machine-driver-hyperkit 还没有进入 Homebrew, 需要手动编译和安装,我就偷个懒,仍然用 xhyve。以后只要在安装和运行的命令中把 xhyve 改为 hyperkit 就可以。

如果你在第一次启动 Minikube 时遇到错误或被中断,后面重试仍然失败时,可以尝试运行 minikube delete 把集群删除,重新来过。

Minikube 启动时会自动配置 kubectl,把它指向 Minikube 提供的 Kubernetes API 服务。可以用下面的命令确认:

$ kubectl config current-context
minikube

Kubernetes 架构简介

典型的 Kubernetes 集群包含一个 master 和很多 node。Master 是控制集群的中心,node 是提供 CPU、内存和存储资源的节点。Master 上运行着多个进程,包括面向用户的 API 服务、负责维护集群状态的 Controller Manager、负责调度任务的 Scheduler 等。每个 node 上运行着维护 node 状态并和 master 通信的 kubelet,以及实现集群网络服务的 kube-proxy。

作为一个开发和测试的环境,Minikube 会建立一个有一个 node 的集群,用下面的命令可以看到:

$ kubectl get nodes
NAME STATUS AGE VERSION
minikube Ready 1h v1.10.0

部署一个单实例服务

我们先尝试像文章开始介绍 Docker 时一样,部署一个简单的服务。Kubernetes 中部署的最小单位是 pod,而不是 Docker 容器。实时上 Kubernetes 是不依赖于 Docker 的,完全可以使用其他的容器引擎在 Kubernetes 管理的集群中替代 Docker。在与 Docker 结合使用时,一个 pod 中可以包含一个或多个 Docker 容器。但除了有紧密耦合的情况下,通常一个 pod 中只有一个容器,这样方便不同的服务各自独立地扩展。

Minikube 自带了 Docker 引擎,所以我们需要重新配置客户端,让 docker 命令行与 Minikube 中的 Docker 进程通讯:

$ eval $(minikube docker-env)

在运行上面的命令后,再运行 docker image ls 时只能看到一些 Minikube 自带的镜像,就看不到我们刚才构建的 docker-demo:0.1 镜像了。所以在继续之前,要重新构建一遍我们的镜像,这里顺便改一下名字,叫它 k8s-demo:0.1。

$ docker build -t k8s-demo:0.1 .

然后创建一个叫 pod.yml 的定义文件:

apiVersion: v1
kind: Pod
metadata:
name: k8s-demo
spec:
containers:
- name: k8s-demoimage: k8s-demo:0.1ports:- containerPort: 80

这里定义了一个叫 k8s-demo 的 Pod,使用我们刚才构建的 k8s-demo:0.1 镜像。这个文件也告诉 Kubernetes 容器内的进程会监听 80 端口。然后把它跑起来:

$ kubectl create -f pod.yml
pod "k8s-demo" created

kubectl 把这个文件提交给 Kubernetes API 服务,然后 Kubernetes Master 会按照要求把 Pod 分配到 node 上。用下面的命令可以看到这个新建的 Pod:

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
k8s-demo 1/1 Running 0 5s

因为我们的镜像在本地,并且这个服务也很简单,所以运行 kubectl get pods 的时候 STATUS 已经是 running。要是使用远程镜像(比如 Docker Hub 上的镜像),你看到的状态可能不是 Running,就需要再等待一下。

虽然这个 pod 在运行,但是我们是无法像之前测试 Docker 时一样用浏览器访问它运行的服务的。可以理解为 pod 都运行在一个内网,我们无法从外部直接访问。要把服务暴露出来,我们需要创建一个 Service。Service 的作用有点像建立了一个反向代理和负载均衡器,负责把请求分发给后面的 pod。

创建一个 Service 的定义文件 svc.yml:

apiVersion: v1
kind: Service
metadata:
name: k8s-demo-svc
labels:
app: k8s-demo
spec:
type: NodePort
ports:
- port: 80nodePort: 30050
selector:
app: k8s-demo

这个 service 会把容器的 80 端口从 node 的 30050 端口暴露出来。注意文件最后两行的 selector 部分,这里决定了请求会被发送给集群里的哪些 pod。这里的定义是所有包含「app: k8s-demo」这个标签的 pod。然而我们之前部署的 pod 并没有设置标签:

$ kubectl describe pods | grep Labels
Labels: <none>

所以要先更新一下 pod.yml,把标签加上(注意在 metadata: 下增加了 labels 部分):

apiVersion: v1
kind: Pod
metadata:
name: k8s-demo
labels:
app: k8s-demo
spec:
containers:
- name: k8s-demoimage: k8s-demo:0.1ports:- containerPort: 80

然后更新 pod 并确认成功新增了标签:

$ kubectl apply -f pod.yml
pod "k8s-demo" configured
$ kubectl describe pods | grep Labels
Labels: app=k8s-demo

然后就可以创建这个 service 了:

$ kubectl create -f svc.yml
service "k8s-demo-svc" created

用下面的命令可以得到暴露出来的 URL,在浏览器里访问,就能看到我们之前创建的网页了。

$ minikube service k8s-demo-svc --url
http://192.168.64.4:30050

横向扩展、滚动更新、版本回滚

在这一节,我们来实验一下在一个高可用服务的生产环境会常用到的一些操作。在继续之前,先把刚才部署的 Pod 删除(但是保留 service,下面还会用到):

$ kubectl delete pod k8s-demo
pod "k8s-demo" deleted

在正式环境中我们需要让一个服务不受单个节点故障的影响,并且还要根据负载变化动态调整节点数量,所以不可能像上面一样逐个管理 pod。Kubernetes 的用户通常是用 Deployment 来管理服务的。一个 deployment 可以创建指定数量的 pod 部署到各个 node 上,并可完成更新、回滚等操作。

首先我们创建一个定义文件 deployment.yml:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: k8s-demo-deployment
spec:
replicas: 10
template:
metadata:labels:app: k8s-demo
spec:containers:- name: k8s-demo-podimage: k8s-demo:0.1ports:- containerPort: 80

注意开始的 apiVersion 和之前不一样,因为 Deployment API 没有包含在 v1 里,replicas: 10 指定了这个 deployment 要有 10 个 pod,后面的部分和之前的 pod 定义类似。提交这个文件,创建一个 deployment:

$ kubectl create -f deployment.yml
deployment "k8s-demo-deployment" created

用下面的命令可以看到这个 deployment 的副本集(replica set),有 10 个 Pod 在运行。

$ kubectl get rs
NAME DESIRED CURRENT READY AGE
k8s-demo-deployment-774878f86f 10 10 10 19s

假设我们对项目做了一些改动,要发布一个新版本。这里作为示例,我们只把 HTML 文件的内容改一下, 然后构建一个新版镜像 k8s-demo:0.2:

$ echo '<h1>Hello Kubernetes!</h1>' > html/index.html
$ docker build -t k8s-demo:0.2 .

然后更新 deployment.yml:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: k8s-demo-deployment
spec:
replicas: 10
minReadySeconds: 10
strategy:
type: RollingUpdate
rollingUpdate:maxUnavailable: 1maxSurge: 1
template:
metadata:labels:app: k8s-demo
spec:containers:- name: k8s-demo-podimage: k8s-demo:0.2ports:- containerPort: 80

这里有两个改动,第一个是更新了镜像版本号 image: k8s-demo:0.2,第二是增加了 minReadySeconds: 10 和 strategy 部分。新增的部分定义了更新策略:minReadySeconds: 10 指在更新了一个 pod 后,需要在它进入正常状态后 10 秒再更新下一个 pod;maxUnavailable: 1 指同时处于不可用状态的 pod 不能超过一个;maxSurge: 1 指多余的 Pod 不能超过一个。这样 Kubernetes 就会逐个替换 service 后面的 pod。运行下面的命令开始更新:

$ kubectl apply -f deployment.yml --record=true
deployment "k8s-demo-deployment" configured

这里的 --record=true 让 Kubernetes 把这行命令记到发布历史中备查。这时可以马上运行下面的命令查看各个 Pod 的状态:

$ kubectl get pods
NAME READY STATUS ... AGE
k8s-demo-deployment-774878f86f-5wnf4 1/1 Running ... 7m
k8s-demo-deployment-774878f86f-6kgjp 0/1 Terminating ... 7m
k8s-demo-deployment-774878f86f-8wpd8 1/1 Running ... 7m
k8s-demo-deployment-774878f86f-hpmc5 1/1 Running ... 7m
k8s-demo-deployment-774878f86f-rd5xw 1/1 Running ... 7m
k8s-demo-deployment-774878f86f-wsztw 1/1 Running ... 7m
k8s-demo-deployment-86dbd79ff6-7xcxg 1/1 Running ... 14s
k8s-demo-deployment-86dbd79ff6-bmvd7 1/1 Running ... 1s
k8s-demo-deployment-86dbd79ff6-hsjx5 1/1 Running ... 26s
k8s-demo-deployment-86dbd79ff6-mkn27 1/1 Running ... 14s
k8s-demo-deployment-86dbd79ff6-pkmlt 1/1 Running ... 1s
k8s-demo-deployment-86dbd79ff6-thh66 1/1 Running ... 26s

从 AGE 列就能看到有一部分 Pod 是刚刚新建的,有的 Pod 则还是老的。下面的命令可以显示发布的实时状态:

$ kubectl rollout status deployment k8s-demo-deployment
Waiting for rollout to finish: 1 old replicas are pending termination...
Waiting for rollout to finish: 1 old replicas are pending termination...
deployment "k8s-demo-deployment" successfully rolled out

由于我输入得比较晚,发布已经快要结束,所以只有三行输出。下面的命令可以查看发布历史,因为第二次发布使用了 --record=true 所以可以看到用于发布的命令。

$ kubectl rollout history deployment k8s-demo-deployment
deployments "k8s-demo-deployment"
REVISION CHANGE-CAUSE
1 <none>
2 kubectl apply --filename=deploy.yml --record=true

这时如果刷新浏览器,就可以看到更新的内容「Hello Kubernetes!」。假设新版发布后,我们发现有严重的 bug,需要马上回滚到上个版本,可以用这个很简单的操作:

$ kubectl rollout undo deployment k8s-demo-deployment --to-revision=1
deployment "k8s-demo-deployment" rolled back

Kubernetes 会按照既定的策略替换各个 Pod,与发布新版本类似,只是这次是用老版本替换新版本:

$ kubectl rollout status deployment k8s-demo-deployment
Waiting for rollout to finish: 4 out of 10 new replicas have been updated...
Waiting for rollout to finish: 6 out of 10 new replicas have been updated...
Waiting for rollout to finish: 8 out of 10 new replicas have been updated...
Waiting for rollout to finish: 1 old replicas are pending termination...
deployment "k8s-demo-deployment" successfully rolled out

在回滚结束之后,刷新浏览器就可以确认网页内容又改回了「Hello Docker!」。

结语

我们从不同层面实践了一遍镜像的构建和容器的部署,并且部署了一个有 10 个容器的 deployment, 实验了滚动更新和回滚的流程。Kubernetes 提供了非常多的功能,本文只是以走马观花的方式做了一个快节奏的 walkthrough,略过了很多细节。虽然你还不能在简历上加上「精通 Kubernetes」,但是应该可以在本地的 Kubernetes 环境测试自己的前后端项目,遇到具体的问题时求助于 Google 和官方文档即可。在此基础上进一步熟悉应该就可以在别人提供的 Kubernetes 生产环境发布自己的服务。

LeanCloud 的大部分服务都运行在基于 Docker 的基础设施上,包括各个 API 服务、中间件、后端任务等。大部分使用 LeanCloud 的开发者主要工作在前端,不过云引擎是我们的产品中让容器技术离用户最近的。云引擎提供了容器带来的隔离良好、扩容简便等优点,同时又直接支持各个语言的原生依赖管理,为用户免去了镜像构建、监控、恢复等负担,很适合希望把精力完全投入在开发上的用户。

本文转自DockOne-Docker 和 Kubernetes 从听过到略懂:给程序员的旋风教程

Docker 和 Kubernetes 从听过到略懂:给程序员的旋风教程相关推荐

  1. k8s之Pod详解(五)【Kubernetes(K8S) 入门进阶实战完整教程,黑马程序员K8S全套教程(基础+高级)】

    参考于Kubernetes(K8S) 入门进阶实战完整教程,黑马程序员K8S全套教程(基础+高级) Pod Pod的结构 每个Pod中都可以包含一个或者多个容器 这些容器可以分为两类: 用户自定义用的 ...

  2. 2019年技术盘点容器篇(一):听UCloud谈风生水起的K8S | 程序员硬核评测

    戳蓝字"CSDN云计算"关注我们哦! 作者:刘晶晶 据相关调研机构出具的报告数据显示,目前应用容器市场规模将从2016年的 7.62亿美元增长到2020年的27亿美元.显而易见,引 ...

  3. 支付宝集福攻略,作为程序员的你集福了么?

    哇哦 一年一度的支付宝集五福活动 又双叒上线啦~ 2022年的支付宝集五福 在1月19日零点已经正式开始 最终在除夕夜 也就是1月31日22点18分开奖 合成五福可以获得红包奖励 虽然去年我只拿到了2 ...

  4. java什么都听过_【Java】程序员最大的悲哀是什么?

    最大的悲哀是:听谁的都得死. 1.在学校,你听信别人,觉得这行好找工作.于是考试混过,忙家教忙谈女朋友-- 结局:临毕业企业不要你,因为你什么都不会.毕业等于失业.死. 2.在学校,你总算把技术学进去 ...

  5. 程序员求职攻略(《程序员面试笔试宝典》)之面试笔试技巧?

    不是看了本C语言编程书籍,就可以说精通C语言,会写一句hello world,就可以自称程序员,程序员是一种职业,更是一种精神.他们天资聪颖,不拘小节:他们个性十足,幽默风趣:他们工作努力,任劳任怨. ...

  6. 程序员求职攻略(《程序员面试笔试宝典》)之面试心得交流?

    "前车之鉴,后事之师",每一个成功的经验都能成为后来师弟师妹.学弟学妹学习的榜样,而每一次失败的经历也能给予后来者血的教训.本章以各大名牌高校.研究所的应届毕业生的亲身求职经历与体 ...

  7. 程序员求职攻略(《程序员面试笔试宝典》)之自己的强项或是研究方向与中意的工作岗位不一致怎么办?...

    戏如人生,人生如戏,只要找对了角色,何妨一直畅演下去? --<新龙门客栈> 无论是本科生还是研究生,很多人在毕业求职的时候,都会发现一个奇怪的现象:自己对C语言很熟悉,很擅长,可是中意的企 ...

  8. 程序员修神之路--打通Docker镜像发布容器运行流程

    菜菜哥,我看了一下docker相关的内容,但是还是有点迷糊 还有哪不明白呢? 如果我想用docker实现所谓的云原生,我的项目该怎么发布呢? 这还是要详细介绍一下docker了 Docker 是一个开 ...

  9. 带你快速了解 Docker 和 Kubernetes

    作者:honghaohu,腾讯 PCG 后台开发工程师 从单机容器化技术 Docker 到分布式容器化架构方案 Kubernetes,当今容器化技术发展盛行.本文面向小白读者,旨在快速带领读者了解 D ...

最新文章

  1. python浪漫代码-使用Python代码的程序员也浪漫
  2. Spring - @CompentScan全解
  3. 【转】.NET Core全面扫盲贴
  4. 使用nodejs和Java访问远程服务器的服务
  5. 解决jquery调用NET webservice跨域的问题
  6. Socket通信学习(二):序列化与反序列化
  7. 【马来西亚】娘惹的含义
  8. 封装条形码MaHelper
  9. 时间基准控件外观的设置纠正
  10. 完美黑苹果clover EFI BigSur11.2 神舟K580c i5 BCM94360HMB WIFI蓝牙二合一网卡
  11. 谁曾从谁的时光里停留
  12. android虚拟手机云之二:应用多开
  13. UE4对接腾讯GME语音服务(实时语音一)
  14. Debian+Apache2+MySQL5+PHP5+GD
  15. 『杭电1859』最小长方形
  16. sklearn.neighbors.KNeighborsClassifier()函数解析
  17. 智能停车场ARM工控主板应用
  18. 带宽、流量限制软件之Negies中文使用教程
  19. 基于JAVA图书共享系统计算机毕业设计源码+数据库+lw文档+系统+部署
  20. 数据可视化系列-05数据分析报告

热门文章

  1. pip install python-docx报错_python各种模块的安装
  2. easyui数据表格显示复选框_WinCC 报警控件、在线趋势/表格控件数据查询
  3. Xilinx DMA kernel 驱动详解 (一)
  4. linux install命令文件夹,详解Linux系统中的install命令的用法
  5. linux批量用户创建,linux 批量用户的创建
  6. python画彩色城墙_Python绘制城堡-(有惊喜!!!)
  7. 算一串数字的entropy_2020520|一个万年一遇有爱的日子,网友:就是一串数字
  8. 【 Vivado 】基本的时序约束、分析的概念
  9. 【 ML 】Steepest Descent Iteration Procedure of TOA - Based Positioning Simulation
  10. 【 FPGA 】FIR滤波器目录