刘华:戏说Docker和K8s,一文让你成为懂王

Posted 敏于思 捷于行

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了刘华:戏说Docker和K8s,一文让你成为懂王相关的知识,希望对你有一定的参考价值。

 一文让你成为Docker和K8s的懂王!



最近,K8s和Docker的离婚案闹得大伙心慌慌的。两位改变世界的神仙打架,咱码农会不会被误伤呢?万幸的是,Docker生的娃(镜像)K8s还是会继续照料,只要Docker没变心(继续遵循Open Container Initiative,OCI标准),我们还是可以继续安心用Docker生成镜像,然后部署在K8s上。


两位神仙的恩恩怨怨暂且不表,留待最后再谈。先来跟大伙科普一下这俩主是干嘛的,为啥改变了世界,对我们又有啥用呢?




01

Docker能咋伺候咱?


还记得咱小时候家里不富裕,买个电脑都是找人把配件东凑凑、西凑凑的,拼成一台兼容机,然后装上个Windows的。那个时候我们的记忆是,拼装硬件几十分钟搞掂,但装个Windows弄个好几小时。后来有了Ghost,结果就鬼那么快把Windows装好了。


Ghost是个什么鬼,居然有这等魔力?Ghost的法术就是镜像(Image)。把别人装好系统的硬盘拷贝压缩成镜像文件,然后在新的电脑上解压拷贝,搞掂。还能把别人电脑里的好东西一起搬过来。这就是镜像的魔力。


Docker就是掌握了镜像的魔力!我们开发出来的应用程序,是需要一个运行环境的。在这个运行环境中,OS、用户、用户组、权限、基础软件、证书等等样样都得有,这个配置过程往往并不简单,而且应用程序经常要搬家,开发镇、测试镇、生产镇、灾备镇每天可能得跑几回。


没有Docker的时候,应用部署到不同环境就像带着行李入住各个镇的酒店,每次都要收拾打包,经常丢三落四的。每个镇的酒店的摆设和条件都不一样,也要不断调整和适应。上云以后,搬家更频繁了,适应和收拾也要更快了,想想头都大。


因为这个过程是很重复的,所以我们发明了一些自动化打包和部署工具,试图让电脑帮我们把这些繁杂的过程搞掂。但自动化工具只会干计划好的重复的活,条件不一样,就得再弄新的脚本,结果脚本越来越多。而且如果过程比较复杂的时候,自动化也不好使,有时也会闹脾气,半路撩杆子。


有了Docker,就像开着房车四处逛,应用软件所需要的所有细软都在车上,到哪都不需要重新打包收拾,到每个镇上借点水电就好。每天跑多少趟都不嫌累。


Docker就是能让你把应用程序和运行环境完整地封装在一起,可以把运行环境带在身上到处走,不怕水土不服。


这房车怎样布置呢?开发人员只要设计好了Dockerfile,Docker就会照着布置。下面是个样例:

FROM openjdk:8-alpine
ARG NAMEARG VERSIONARG JAR_FILE
LABEL name=$NAME version=$VERSION
# 设定时区ENV TZ=Asia/ShanghaiRUN set -eux; ln -snf /usr/share/zoneinfo/$TZ /etc/localtime; echo $TZ > /etc/timezone
# 新建用户java-appRUN set -eux; addgroup --gid 1000 java-app; adduser -S -u 1000 -g java-app -h /home/java-app/ -s /bin/sh -D java-app; mkdir -p /home/java-app/lib /home/java-app/etc /home/java-app/jmx-ssl /home/java-app/logs /home/java-app/tmp /home/java-app/jmx-exporter/lib /home/java-app/jmx-exporter/etc; chown -R java-app:java-app /home/java-app
# 导入启动脚本COPY --chown=java-app:java-app docker-entrypoint.sh /home/java-app/docker-entrypoint.sh
# 导入JARCOPY --chown=java-app:java-app target/${JAR_FILE} /home/java-app/lib/app.jar
USER java-app
ENTRYPOINT ["/home/java-app/docker-entrypoint.sh"]
EXPOSE 8080
这个Dockerfile就是告诉Docker怎么制作一个镜像,它包含了以下内容:
  1. 指定一个基础镜像(含OS和基础软件,如JDK)

  2. 设定正确的时区

  3. 创建用户和配置权限

  4. 设置JVM参数、Java System Properties、程序自定义的参数

  5. 上传应用jar文件

  6. 启动应用

  7. 指定Web程序的接口

创建镜像:

docker build . -t sample-app:latest

通过Docker build命令构建一个镜像,并以sample-app:latest给它标签(latest是版本)。


试运行:

docker run -p 80:8080 sample-app:latest

通过Docker run命令运行镜像中的应用,并把容器里的8080端口通过80端口暴露出来,现在可以通过http://localhost来访问该应用了。


推送到镜像仓库:

docker push sample-app:latest

通过Docker push命令把镜像从本地推送到公共镜像仓库。


从镜像仓库拉取并运行:

docker pull sample-app:latestdocker run -p 80:8080 sample-app:latest

在任何可运行Docker的服务器,通过Docker pull从公共镜像仓库拉取镜像,并运行。


所以整个过程就是:

在本地(含有已能成功运行应用的开发环境):构建Docker镜像 -> 在本地运行镜像进行验证 -> 把镜像推送到镜像仓库;

在服务器:从镜像仓库拉取镜像 -> 运行镜像。


就这么简单!


如果我们有几个应用要一起运行,还可以组个车队:

version: '3'services: web:    build: nginx ports: - "80:8080" redis:    image: redis

过编写以上的d ocker- compose.yml配置,可以把 若干个 Docker 容器 组装在一起运行, 在这个例子中, web(镜像是nginx,容器端口8080映射为80 r edis(镜像是redis 两个容器 一起运行。 通过 docker-c ompose up命令启动, docker-compose  down命令 停止


容器技术其实并不新鲜,所谓容器就是特殊的线程,分配给一组程序独立运行,和其他的线程完全隔离。Docker的最大优势是引入了镜像(Image),让开发者可以把应用运行所需要的环境,包括OS打包到一个镜像中,在所有支持Docker的服务器上运行和迁移。镜像的好处是:

  1. 简化了构建、打包和部署过程。一般来说,全量变更比增量变更要简单得多,因为你不需要关心两个版本间的差异。Docker镜像部署就是全量变更(虽然Docker镜像是分层的,只有有变动的层会被拉取或推送,以节约时间和带宽,但这个过程由Docker管理,开发者不需要关心);


  2. 由于Docker镜像包含应用运行的整个环境并且可以运行在所有支持它的Linux服务器上,对底层的服务器和OS没有依赖关系。保证了环境的隔离性和一致性,并完美地切合了不可变基础设施的原则。


  3. 通过Docker镜像,可以以秒级这样的速度在新的服务器上配置、部署和运行应用,确保了水平扩展的能力,这也是云原生的要求。


在Windows上安装Docker最简单的方式就是下载和安装Docker Desktop。




02

K8s又是何方神圣?


Docker那么厉害,咋后来名声又都给了K8s了呢?听说Kubernetes在希腊语是领航员的意思,它要带咱去哪飞啊?


过去,开发人员觉得只生一个娃好,只需要开发一个单体应用,把所有精力、钱财和资源都给了这个应用,让它过关斩将,一夫当关,万夫莫开,应对各类妖怪(俗称业务需求和用户请求)。但时代不一样了,妖怪的种类和数量都越来越多,一个应用单枪匹马,纵然长出了三头六臂都难以招架。


开发人员就把原来的单体应用按业务服务类型给拆了,而且用docker-compose让各应用组个队,一起上,但还是招架不住,恨不得有分身术,以一化十,以十化百。


开发人员决定开发出不同技能的更小粒度的应用程序,然后让它们组个大兵团,不同技能的应用精心修炼好一门绝技,各司其职,负责打不同的怪物,而且掌握分身术,随时变出更多的分身,应对更多怪物。


这个时候,事情就复杂了,需要有个大统领指挥,收放自如。这差事,本来Docker也想揽,但没揽住。docker-compose管个小家庭凑合。这么大的营生,只能望而却步。


后来半路杀出个程咬金,K8s横空出世,当了这个大统领。人多是不是,活杂是不是,都不在话下。引无数码农竞折腰。


K8s咋那么神呢?咱先来看看大神有啥五脏六腑(这张图对于理解K8s很重要!):


刘华:戏说Docker和K8s,一文让你成为懂王



  • Deployment - 给容器的房子装修的施工队,我们画张图纸(包含房子Pod的名字,标签,要部署的Docker镜像,房子Pod的副本数量Replicas等),Deployment拉上施工队就干活,保质保量。


  • Service - 大统领日理万机,要分配活给容器们,总不能一间间房子找人。于是找些楼长来管管事。楼长知道哪个妖怪来了找哪些房子的容器(某个微服务),他掌握了和容器的接头暗号(房子Pod的标签和容器的Port端口)。



  • Node - 房子所在大楼的桩,桩越多,大楼越结实,可以安排更多的房子(一个Node就是一台服务器,通过增加Node可以加强整个集群的能力)。


K8s大统领还有一个大招,就是咱要干啥只需要给他吩咐,他就帮咱干好,不需要告诉他咋做。比方说,咱要他盖房子:


Deployment (示例文件名:deployment.yaml):

apiVersion: apps/v1kind: Deploymentmetadata: name: k8s-sample-app labels: app: k8s-sample-appspec: selector: matchLabels: app: k8s-sample-app replicas: 5 template: metadata: labels: app: k8s-sample-app spec: containers: - name: k8s-test image: k8s-test:latest" imagePullPolicy: Always ports: - containerPort: 5000 protocol: TCP

文件里的关键要素:

  • 这是一个Deployment文件,用来创建Pod的模板。

  • 它的标签(label)是app: k8s-sample-app。Service会用这个标签来找Pod,并把请求转给这些Pod。

  • 用来部署和允许的容器镜像是k8s-test:latest

  • 容器的端口是5000。

  • 水平扩展(replicas)是5,意味着将有5个Pod副本运行。


安排个楼长:

Service (示例文件名:service.yaml):

apiVersion: v1kind: Servicemetadata: name: k8s-sample-svc annotations: cloud.google.com/neg: '{"ingress": true}'spec: ports: - name: host1 port: 80 protocol: TCP targetPort: 5000 selector: app: k8s-sample-app type: NodePort

文件中的关键要素:

  • 这是类型为NodePort的Service(Service有分NodePort、ClusterIP和Load Balance三类,这里不展开了)。它将把Pod容器的端口暴露出来,供外界访问。

  • 它的名字是k8s-sample-svc

  • 它会把80端口的访问转发到内部容器端口5000。

  • 它会把请求转发给所有带k8s-sample-app标签的Pod。


安排个大内总管:

Ingress (示例文件名:ingress.yaml):

apiVersion: networking.k8s.io/v1beta1kind: Ingressmetadata: name: ilb-k8s-ingress annotations: ingress.gcp.kubernetes.io/pre-shared-cert: "self-signed" kubernetes.io/ingress.class: "gce-internal" kubernetes.io/ingress.allow-http: "false"spec: rules: - http: paths: - backend: serviceName: k8s-sample-svc servicePort: 80

文件中的关键要素:

  • 它是一个Ingress。

  • 它的名字是ilb-k8s-ingress

  • 它的对外端口是80。

  • 它将把请求转发到名为k8s-sample-svc的Service。


我们可以通过以下命令创建以上的所有元素:

kubectl apply -f deployment.yaml kubectl apply -f service.yaml kubectl apply -f ingress.yaml



通过这些配置,外部请求的路径将是Ingress -> Service -> Pods。


如果Node的数量是3的话,那么意味着底层会有3台服务器在支撑整个集群。这5个Pod将由K8s自动分配到这些Node上(用前面的图做示例,很可能是2个在Node A、2个在Node B、1个在Node C)。


从这一点我们可以看到Pod的水平扩展和Node没有直接的关系(当然我们也可以要求某些Pod只运行或不允许运行在某个Node上,这里就不展开了)。


我们可以随时为K8s集群增减Node,而不影响正在运行的Pod(应用)。


从这个过程我们也能看到,我们只需要通过yaml文件告诉K8s我们想要的最终状态,K8s就会帮我们创建和调整,我们不需要告诉K8s怎么做,这也是声明式配置的力量(就像SQL,我们只需要说要什么,不需要管怎么做到)。


现在我们来看看房子建得咋样了:

kubectl get pods

它会罗列所有的Pod及其状态。


窥探房子里面的情况,包括它建在哪个Node上:

kubectl describe pod [POD_NAME]


查看容器的日志:

kubectl logs [POD_NAME]


也可以对Service、Ingress和Node进行类似的操作:

kubectl get svc kubectl get ingress kubectl get nodes kubectl describe svc [SERVICE-NAME] kubectl describe node [NODE-NAME]



K8s隔离了应用和服务器。我们可以在K8s集群上部署数十个甚至上百个微服务,K8s会帮我们妥善管理这么一个繁杂架构,包括某些服务的伸缩。


这里面涉及的复杂的网络、部署、集群、状态、版本升级、储存等都统统交给了K8s。这也是把简单留给用户,把复杂留给自己的设计。作为应用开发者,可以把更多精力放在开发上。这也是K8s大受欢迎的原因。


K8s提供以下能力:


1. 跨越多台服务器的集群管理。

2. Pod为应用提供了稳健和隔离的运行环境。

3. Pod的伸缩。

4. 应用版本升级的滚动发布。

5. Pod之间的负载均衡。

6. 为不同服务提供单一访问入口。


如果你的应用比较简单,只是拆成了若干个服务,那么docker-compose可以解决问题。如果你的应用是真正的微服务架构,有数十个甚至上百个服务,某些服务又需要水平扩展和伸缩,需要服务发现和负载均衡,那么K8s就能帮你管好这么一个繁杂的架构。


K8s可以帮你在不需要关心底层服务器配置和部署的情况下,让你的繁杂架构有序运作,并充分利用底层服务器的资源,它本身就是一层操作系统。目前各主流云厂商都提供了K8s服务。


另外有一点要特别指出的是,容器的本质就是线程,一个Docker容器虽然自带OS,但运行起来是单线程的,并不能模拟一台虚拟机。而K8s的Pod能为部署在里面的容器提供不同的线程和共享的网络和存储,它才是一台虚拟机的代表,并能水平扩展。


当然,K8s是复杂的,特别对于初学者,光是看到那些新概念就晕。所以,只有当你真的有一个繁杂的微服务架构需要管理,才需要考虑K8s。




03

总结


在Docker和K8s的加持下,我们可以通过以下方式部署和运行我们的应用程序:


  1. 在本地开发电脑上安装Docker Desktop。

  2. 为应用创建一个Docker镜像,并推送到镜像仓库;

  3. 在服务器上,安装Docker。然后从镜像仓库拉取镜像并运行。

  4. 可以通过docker-compose运行一组的镜像(应用);

  5. 如果需要管理繁杂的微服务架构,K8s将是首选之一。




番外

关于Docker与K8s的恩怨


最后讲讲K8s和Docker“离婚”的事情,其实并不是K8s和Docker的直接恩怨,而是Docker和整个PaaS江湖的恩怨。


容器技术并不是什么新鲜事物,它本质上就是隔离的线程,基础是Linux的Cgroups、Namespace技术,存在多时。


而能帮助大家把应用程序以“沙盒”这样的隔离形式托管在不同运行环境的PaaS能力,一直是各大厂商的兵家必争之地。云兴起以后,这样的需求变得更加急切。


Docker的横空出世,一举成名就在于它以镜像方式创建容器,大大简化了应用托管过程。由于得到业内的迅速追捧,Docker也想乘胜追击,拿下PaaS霸主的地位。毕竟,Docker如果仅仅是一个镜像打包工具,它无法商业化(zuan qian),它必须争夺容器编排市场。它也在打造和收购自己的容器编排工具,前文提到的docker-compose就是其中一员,还有Swarm。


K8s出来后,一举成为容器编排的一哥。早期它的编排能力也是依托在Docker的运行时(runtime)引擎。由于Docker也想争夺这个市场,它的运行时架构变得越来越复杂,也不支持通用的容器运行时接口(ORI),影响了K8s的架构和效率。


于是K8s选择了抛弃Docker的运行时引擎


由于K8s对容器镜像的调用早已经从Docker实现换成更通用的OCI(Open Container Initiative)接口,所以只要Docker继续遵循OCI,通过Docker打包的镜像在K8s上部署和运行就不会受到影响,这也是为什么说这次“离婚”案对开发人员没有影响。



觉得文章不错,顺手点个“点赞”、“在看”或转发给朋友们吧。


刘华:戏说Docker和K8s,一文让你成为懂王

近期必读:










关于作者



刘华(Kenneth)


  • 就职于世界500强银行,负责基金服务业务软件开发与交付

  • 敏捷、精益、DevOps专家

  • 精通极限编程、Scrum、看板方法、测试驱动开发、持续集成、行为驱动开发、DevOps工具栈

  • 曾在GDevOps、DevOpsDays Meetup、中国软件技术大会、ArchSummit、Top 100等论坛发表主题演讲

  • 阿里云、谷歌云认证架构师

  • 著有《猎豹行动:硝烟中的敏捷转型之旅》一书和专栏《软件交付那些事儿》


刘华:戏说Docker和K8s,一文让你成为懂王



关注公众号看其他原创作品
敏于思 捷于行 
坚持原创高质量软件交付相关文章

觉得好看,点个“ 点赞 ”、“ 在看 ”或转发给朋友们,欢迎你 留言

以上是关于刘华:戏说Docker和K8s,一文让你成为懂王的主要内容,如果未能解决你的问题,请参考以下文章

Docker与k8s的恩怨情仇—— “服务发现”大法让你的内外交互原地起飞

戏说代理模式

k8s 和 Docker 是什么关系?

Docker与k8s的恩怨情仇—成为PaaS前浪的Cloud Foundry

Docker与k8s的恩怨情仇—成为PaaS前浪的Cloud Foundry

Docker环境搭建,K8s