k8s和docker学习顺序,k8s和docker什么关系

首页 > 技术 > 作者:YD1662023-04-18 02:16:57

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

k8s和docker学习顺序,k8s和docker什么关系(1)

最近,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 NAME ARG VERSION ARG JAR_FILE LABEL name=$NAME \ version=$VERSION # 设定时区 ENV TZ=Asia/Shanghai RUN set -eux; \ ln -snf /usr/share/zoneinfo/$TZ /etc/localtime; \ echo $TZ > /etc/timezone # 新建用户java-app RUN 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 # 导入JAR COPY --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程序的接口

创建镜像:

dockerbuild.-tsample-app:latest

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

试运行:

dockerrun-p80:8080 sample-app:latest

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

推送到镜像仓库:

docker push sample-app:latest

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

从镜像仓库拉取并运行:

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

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

所以整个过程就是:

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

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

就这么简单!

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

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

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

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

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

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

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

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

02 K8s又是何方神圣?

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

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

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

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

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

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

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

k8s和docker学习顺序,k8s和docker什么关系(2)

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

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

apiVersion: apps/v1 kind: Deployment metadata: name: k8s-sample-app labels: app: k8s-sample-app spec: 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: v1 kind: Service metadata: 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/v1beta1 kind: Ingress metadata: 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 kubectlgetnodes kubectldescribesvc[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服务,提供介于IaaS和PaaS之间的能力。

另外有一点要特别指出的是,容器的本质就是线程,一个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上部署和运行就不会受到影响,这也是为什么说这次“离婚”案对开发人员没有影响。

觉得文章不错,顺手转发给朋友们吧。

k8s和docker学习顺序,k8s和docker什么关系(3)

近期必读:

关于作者


刘华(Kenneth)

栏目热文

文档排行

本站推荐

Copyright © 2018 - 2021 www.yd166.com., All Rights Reserved.