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

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

Docker 速成

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

$ brew cask install docker
复制代码

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

$ docker --version
Docker version 18.03.1-ce, build 9ee9f40
复制代码

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

$ 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

而后就能够构建镜像了:后端

$ docker build -t docker-demo:0.1 .
复制代码

请确保你按照上面的步骤为这个实验新建了目录,而且在这个目录中运行 docker build。若是你在其它有不少文件的目录(好比你的用户目录或者 /tmp )运行,docker 会把当前目录的全部文件做为上下文发送给负责构建的后台进程。api

这行命令中的名称 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-demo
      image: k8s-demo:0.1
      ports:
        - 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: 80
      nodePort: 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-demo
      image: k8s-demo:0.1
      ports:
        - 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-pod
          image: k8s-demo:0.1
          ports:
            - 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: 1
      maxSurge: 1
  template:
    metadata:
      labels:
        app: k8s-demo
    spec:
      containers:
        - name: k8s-demo-pod
          image: k8s-demo:0.2
          ports:
            - containerPort: 80
复制代码

这里有两个改动,第一个是更新了镜像版本号 image: k8s-demo:0.2,第二是增长了 minReadySeconds: 10strategy 部分。新增的部分定义了更新策略: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 的开发者主要工做在前端,不过云引擎是咱们的产品中让容器技术离用户最近的。云引擎提供了容器带来的隔离良好、扩容简便等优势,同时又直接支持各个语言的原生依赖管理,为用户免去了镜像构建、监控、恢复等负担,很适合但愿把精力彻底投入在开发上的用户。

LeanCloud 在招聘如下职位:

市场团队负责人

后端软件工程师(Clojure、Python、Java)

Android 软件工程师

具体的需求以及其余正在招聘的职位请见咱们的工做机会页面。除了在官网上能够看到的已经发布的产品外,咱们也在开发让人兴奋的新产品,有不少有意义、有价值的工做。

若是转载本文,请包含原文连接和招聘信息。

相关文章
相关标签/搜索