:mannotop docker – manno的博客

标签: docker

k8s中的Pod

Pod定义

在Kubernetes集群中,Pod是所有业务类型的基础,也是K8S管理的最小单位级,它是一个或多个容器的组合。这些容器共享存储、网络和命名空间,以及如何运行的规范。在Pod中,所有容器都被同一安排和调度,并运行在共享的上下文中。对于具体应用而言,Pod是它们的逻辑主机,Pod包含业务相关的多个应用容器。

Pod有两个必须知道的特点。

  • 网络每一个Pod都会被指派一个唯一的Ip地址,在Pod中的每一个容器副本共享网络命名空间,包括Ip地址和网络端口。在同一个Pod中的容器可以同locahost进行互相通信。当Pod中的容器需要与Pod外的实体进行通信时,则需要通过端口等共享的网络资源。
  • 存储:Pod能够被指定共享存储卷的集合,在Pod中所有的容器能够访问共享存储卷,允许这些容器共享数据。存储卷也允许在一个Pod持久化数据,以防止其中的容器需要被重启。

Pod的调度:K8s一般不直接创建Pod,而是通过控制器和模版配置来管理和调度。通过nodeSelector(节点选择器)选择符合模板条件的Node以创建Pod。

Pod的生命周期等更多信息请查看官方文档

了解K8s资源对象

学习 K8S 首先最重要的是学习各种资源对象的功能,如何编写并创建他们。

那么第一个问题就来了,什么是 K8S 资源对象?

当你使用 kubectl api-resources,就可以列出当前集群中所有的资源定义。

当前集群的资源数量达 73 种,随着你后面安装越来越多的插件后,这个数量会快速增长。

以 K8S 中的核心对象 Pod 为例,对这些字段做一些解释,首先是

  • APIVERSION:v1,对应 yaml 中的 apiVersion
  • KIND:Pod,对应 yaml 中的 kind

使用Pod

编写Pod模板

使用 kubectl explain pod ,可以输出资源对应的属性字段及定义,它在定义资源配置文件时候非常有用。

以 Pod 为例:

可以看到,Pod 的一级字段主要分成 5 个部分:

  1. apiVersion:api 版本,可以通过 kubectl api-resources 查询,或者直接看 explain 的结果
  1. kind:资源类型,可以通过 kubectl api-resources 查询,或者直接看 explain 的结果
  1. metadata:元信息,比如 name, namespace, label 和 annotation 等
  1. spec:资源的具体配置,比如磁盘、网络、镜像等
  1. status:存储一些正在运行的对象的一些状态信息

用户在定义一个资源对象的配置文件时, 只需要写前面 4 个部分,而无需定义 status 部分,因为它是由具体的程序去负责更新维护的,对于用户而言,它是只读的,不可写入。即使你在配置文件中写了这部分内容,也会直接被忽略。

而对于前面 4 个部分:

  • 前面两个字段 apiVersion 和 kind 都是简单字段,值是一个字段串
  • 后面两个字段 metadata 和 spec 是复杂字段,值是一个 object 对象

一个最简单的Pod模板例子:

# simple-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.14.2
    ports:
    - containerPort: 80

除了yaml,可以使用 json或在创建时直接通过std手动输入配置,但为了可读性和可维护性,通常会使用 yaml 格式。

metadata和spec

metadata 和 spec 对象的字段非常多,多到一个屏幕放不到,使用kubectl explain命令计算一下:

  • metadata 的一级字段,有 16 个
  • spec 的一级字段,有 36 个

对象是可以嵌套的,也就是 spec 下的一级字段,还会有二级字段…

非常的恐怖,因此一个资源对象的配置文件,可以复杂到让你头皮发麻。

可以看到上面的字段,实在是太多太杂了,一个对象尚且有这么多字段,那 K8S 中自带的资源对象还有几十个呢,再加一些第三方的自定义资源,学一辈子也学不完啊。

不过,你也不用担心,虽然字段很多,但不同的对象的结构大体相似,我们当前只需要把这些字段给掌握了就好。

至于那些低频的字段,就直接让他缺省就行,并不影响使用。

高频使用的是以下的几个字段

创建Pod

如何创建资源对象

创建一个资源对象的方式有好多种,从调用方式上可以分为两种:

  • 调用 HTTP 接口:用于上层业务的开发
  • 调用 client 命令:即 kubectl 命令行工具

目前对于刚学习的新手来说,kubectl 是熟悉各种资源对象最好的工具,后面我也都会使用它来演示。

使用了 kubectl,创建资源对象,又可以分为两种:

  • kubectl apply 是声明式 API,体现的是我要修改成什么样?对于同一个 pod.yaml apply 完全没有任何问题,若第二次 apply 之前,修改了 pod 中的一些内容,也会更新上去。
  • kubectl create 是命令式 API,体现的是我要怎么样创建?对于同一个 pod.yaml create 多次是会报错的,原因是 k8s 中资源名称必须是唯一的,而该名称的 pod 资源已经创建过了。

手动创建Pod

虽然k8s通常情况下是通过控制器去管理Pod对象,但支持手动创建。

根据上文的yaml例子,可以通过 kubectl apply -f simple-pod.yaml 去手动创建 Pod 对象。

创建完成之后,可以使用kubectl get命令查看刚才创建的 Pod 对象,可以用三种传参查到刚刚的Pod对象:

  • kubectl get po:这里的 po 对应 kubectl api-resources结果中的<SHORTNAMES>
  • kubectl get pod:这里的 pod 对应 kubectl api-resources结果中的<KIND>
  • kubectl get pods:这里的 pod 对应 kubectl api-resources结果中的<NAME>

K8S 对大小写是不敏感的,ab、Ab、aB、AB 都是一样的,因此对于NAME 和 KIND,你可以大小写自由组合,都是没有问题的。

但唯独对于 SHORTNAMES 不可以,只能严格按照定义的小写来

docker概念和应用

docker的提出

传统的kvm通过创建虚拟机的方式来部署多个业务程序,虽然它解决了物理机所不能解决的环境隔离、应用迁移问题,但也带来了大量的硬件资源消耗,因为不同的业务程序所需的资源不同,而每一个虚拟机都是一个完整的操作系统,粒度太重,要分配大量的系统资源,因此,为了高效利用硬件资源,粒度更轻的容器技术诞生了。

*发展历程:物理机——VM(虚拟机)——KVM(虚拟化技术)——容器化技术(Docker)

docker的诞生

Docker公司位于旧金山,原名dotcloud,底层利用了Linux内核支持的容器技术LXC是为了更方便地创建和管理这些容器而开发的一套工具

Docker由Golang编写,基于Linux内核的Cgroups、Namespace、以及Union FS等技术对进程进行环境隔离,是属于操作系统层面的虚拟化技术。

  • 环境隔离:指进程空间、网络空间、文件系统空间独立
  • 被隔离的进程独立于宿主机以及其它隔离的进程,称为容器
  • 容器化的最大优势就是保持轻量级的同时实现了VM一样的环境隔离功能
  • Docker是开源的容器引擎,可以方便地对容器进行管理,最初的Docker是基于LXC的,后转而使用该公司自行开发的LibContainer

容器的特点:

  • 是镜像的实例化
  • 随处运行:容器可以将代码与配置文件和相关依赖库进行打包,从而确保在任何环境下的运行都是一致的。
  • 高资源利用率:容器提供进程级的隔离,因此可以更加精细地设置CPU和内存的使用率,进而更好地利用服务器的计算资源。
  • 快速扩展:每个容器都可作为单独的进程予以运行,并且可以共享底层操作系统的系统资源,这样一来可以加快容器的启动和停止效率。

镜像:包含容器所要运行的程序本身及其依赖、配置项、脚本等一切所需环境和配置

使用docker后的开发—部署流程

开发人员:

  • 编写业务项目程序的本体代码
  • 编写dockerfile,包含shell部署逻辑、项目的运行环境配置、启动程序。
  • docker根据dockerfile构建镜像

测试/运维人员:无需再了解项目的具体配置环境,直接使用开发交付的镜像创建容器实例

总体流程:发布源码-构建镜像-发布镜像-创建容器进程-创建容器实例(源码+镜像=实例)

Docker容器是与系统其他部分隔离开的一系列进程,运行这些进程所需的所有文件都由另一个镜像提供,从开发到测试再到生产的整个过程中,Linux 容器都具有可移植性和一致性。相对于依赖重复传统测试环境的开发渠道,容器的运行速度要快得多,并且支持在多种主流云平台(PaaS)和本地系统上部署。

容器彻底解决了开发、测试、生产等不同环境下环境不一致、环境迁移难、环境部署难的问题

镜像内容只有发行版及其依赖,与宿主机公用内核,相比于虚拟机非常轻量级

Dockerfile

Dockerfile是用来描述文件的构成的文本文档,其中包含了用户可以在使用行调用以组合Image的所有命令,用户还可以使用Docker build实现连续执行多个命令指今行的自动构建。

通过编写Dockerfile生磁镜像,可以为开发、测试团队提供基本一致的环境,从而提升开发、测试团队的效率,不用再为环境不统一而发愁,同时运维也能更加方便地管理我们的镜像。

Dockerfile的语法非常简单,常用的只有11个:

编写优雅的Dockerfile主要需要注意以下几点:

  • Dockerfile文件不宜过长,层级越多最终制作出来的镜像也就越大。
  • 构建出来的镜像不要包含不需要的内容,如日志、安装临时文件等。
  • 尽量使用运行时的基础镜像,不需要将构建时的过程也放到运行时的Dockerfile里。

只要记住以上三点就能写出不错的Dockerfile。

为了方便大家了解,我们用两个Dockerfile实例进行简单的对比:

FROM ubuntu:16.04
RUN apt-get update
RUN apt-get install -y apt-utils libjpeg-dev \     
python-pip
RUN pip install --upgrade pip
RUN easy_install -U setuptools
RUN apt-get clean
FROM ubuntu:16.04
RUN apt-get update && apt-get install -y apt-utils \
  libjpeg-dev python-pip \
           && pip install --upgrade pip \
      && easy_install -U setuptools \
    && apt-get clean

我们看第一个Dockerfile,乍一看条理清晰,结构合理,似乎还不错。再看第二个Dockerfile,紧凑,不易阅读,为什么要这么写?

  • 第一个Dockerfile的好处是:当正在执行的过程某一层出错,对其进行修正后再次Build,前面已经执行完成的层不会再次执行。这样能大大减少下次Build的时间,而它的问题就是会因层级变多了而使镜像占用的空间也变大。
  • 第二个Dockerfile把所有的组件全部在一层解决,这样做能一定程度上减少镜像的占用空间,但在制作基础镜像的时候若其中某个组编译出错,修正后再次Build就相当于重头再来了,前面编译好的组件在一个层里,得全部都重新编译一遍,比较消耗时间。
  • 注意,若Dockerfile非常长的话可以考虑减少层次,因为Dockerfile最高只能有127层。

*多阶构建(了解)

在多阶构建出来之前,传统方式是使用单个文件进行构建,单文件就是将所有的构建过程(包括项目的依赖、编译、测试、打包过程)全部包含在一个Dockerfile中之下:

FROM golang:1.11.4-alpine3.8 AS build-env
ENV GO111MODULE=off
ENV GO15VENDOREXPERIMENT=1
ENV BUILDPATH=github.com/lattecake/hello
RUN mkdir -p /go/src/${BUILDPATH}
COPY ./ /go/src/${BUILDPATH}
RUN cd /go/src/${BUILDPATH} && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go install –v

CMD [/go/bin/hello]

这种的做法会带来一些问题:

  • Dockerfile文件会特别长,当需要的东西越来越多的时候可维护性指数级将会下降;
  • 镜像层次过多,镜像的体积会逐步增大,部署也会变得越来越慢;
  • 代码存在泄漏风险。

以Golang为例,它运行时不依赖任何环境,只需要有一个编译环境,那这个编译环境在实际运行时是没有任务作用的,编译完成后,那些源码和编译器已经没有任务用处了也就没必要留在镜像里。

多文件构建(多阶构建前的解决方案)

多文件构建,其实就是使用多个Dockerfile,然后通过脚本将它们进行组合。假设有三个文件分别是:Dockerfile.run、Dockerfile.build、build.sh

  • Dockerfile.run就是运行时程序所必须需要的一些组件的Dockerfile,它包含了最精简的库;
  • Dockerfile.build只是用来构建,构建完就没用了;
  • build.sh的功能就是将Dockerfile.run和Dockerfile.build进行组成,把Dockerfile.build构建好的东西拿出来,然后再执行Dockerfile.run,算是一个调度的角色。

Dockerfile.build

FROM golang:1.11.4-alpine3.8 AS build-env
ENV GO111MODULE=off
ENV GO15VENDOREXPERIMENT=1
ENV BUILDPATH=github.com/lattecake/hello
RUN mkdir -p /go/src/${BUILDPATH}
COPY ./ /go/src/${BUILDPATH}
RUN cd /go/src/${BUILDPATH} && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go install –v

Dockerfile.run

FROM alpine:latest
RUN apk –no-cache add ca-certificates
WORKDIR /root
ADD hello .
CMD ["./hello"]

Build.sh

#!/bin/sh
docker build -t –rm hello:build . -f Dockerfile.build
docker create –name extract hello:build
docker cp extract:/go/bin/hello ./hello
docker rm -f extract
docker build –no-cache -t –rm hello:run . -f Dockerfile.run
rm -rf ./hello

执行build.sh完成项目的构建。

多文件构建大大减小了镜像的占用空间,但它有三个文件需要管理,维护成本也更高一些。

多阶构建

完成多阶段构建我们只需要在Dockerfile中多次使用FORM声明,每次FROM指令可以使用不同的基础镜像,并且每次FROM指令都会开始新的构建,我们可以选择将一个阶段的构建结果复制到另一个阶段,在最终的镜像中只会留下最后一次构建的结果,这样就可以很容易地解决前面提到的问题,并且只需要编写一个Dockerfile文件。这里值得注意的是:需要确保Docker的版本在17.05及以上。下面我们来说说具体操作。

在Dockerfile里可以使用as来为某一阶段取一个别名”build-env”:

FROM golang:1.11.2-alpine3.8 AS build-env

然后从上一阶段的镜像中复制文件,也可以复制任意镜像中的文件:

COPY –from=build-env /go/bin/hello /usr/bin/hello

看一个简单的例子:

FROM golang:1.11.4-alpine3.8 AS build-env
 
ENV GO111MODULE=off
ENV GO15VENDOREXPERIMENT=1
ENV GITPATH=github.com/lattecake/hello
RUN mkdir -p /go/src/${GITPATH}
COPY ./ /go/src/${GITPATH}
RUN cd /go/src/${GITPATH} && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go install -v
 
FROM alpine:latest
ENV apk –no-cache add ca-certificates
COPY --from=build-env /go/bin/hello /root/hello
WORKDIR /root
CMD ["/root/hello"]

执行docker build -t –rm hello3 .后再执行docker images 

安装使用

k8s概念和架构

Kubernetes 是一个可移植、可扩展的开源容器管理平台,用于管理容器化工作负载和服务,支持声明配置和自动化。它有一个庞大的、快速发展的生态系统。Kubernetes 服务、支持和工具广泛可用。

为什么需要 k8s 以及它能做什么

纯Docker运行模式的局限性:

  • 集群架构,业务容器数量庞大,N个数据节点,如何管理容器部署在哪个最合理的数据节点?容器管理
  • 跨主机通信问题,不同宿主机内的容器之间如何通信和相互调用?服务发现问题
  • 如何实现服务高可用?服务挂了怎么检测到并及时重启,健康监控问题
  • 容器如何实现多副本?多个副本之间如何实现负载均衡?
  • 如何实现服务的滚动升级,保证业务连续性?

k8s 为您提供:

  • 服务发现和负载平衡 Kubernetes 为容器提供一个 DNS 名称和 IP 地址,并使它们可以相互知晓彼此的存在。如果容器的流量很高,Kubernetes 能够负载平衡和分配容器间的网络流量,从而使部署稳定。
  • 存储编排 Kubernetes 允许您自动挂载您选择的存储系统,例如本地存储、公共云提供商等。
  • 自动化上线和回滚 k8s会分步骤地监控应用或配置更改的上线(发布新的镜像),同时监视应用程序的执行状况以确保你不会同时终止所有实例,若上线过程出现问题,k8s会及时回滚到上线前的状态。
  • IPv4/IPv6 双协议栈,支持为Pod和容器分配IPv4或IPv6地址。
  • 自动装箱 您为 Kubernetes 提供了一个节点集群,它可以用来运行容器化任务。您可以指定 Kubernetes 为每个容器分配多少 CPU 和内存 (RAM)。Kubernetes 可以将容器安装到您的节点上,以充分利用您的资源。
  • 健康监控和自我修复 Kubernetes 会重启失败的容器,替换容器,杀死不响应用户定义的健康检查的容器,并且在它们准备好服务之前不会向客户端发布它们。
  • 秘密和配置管理 Kubernetes 允许您存储和管理敏感信息,例如密码、OAuth 令牌和 SSH 密钥。您可以部署和更新机密和应用程序配置,而无需重建容器映像,也无需在堆栈配置中公开机密。
  • 弹性伸缩 根据负载压力自动增加或削减容器数量。

k8s 不是什么

Kubernetes 不是传统的、包罗万象的 PaaS(平台即服务)系统。由于 Kubernetes 在容器级别而不是在硬件级别运行,它提供了一些 PaaS 产品共有的普遍适用的功能,例如部署、扩展、负载平衡,并允许用户集成他们的日志记录、监控和警报解决方案。然而,Kubernetes 并不是单一的,这些默认解决方案是可选的和可插入的。Kubernetes 为构建开发人员平台提供了构建块,但在重要的地方保留了用户的选择和灵活性。

k8s 核心架构组件

*核心架构组件:安装、启动k8s后必定拥有的几个组件

  • etcd:k8s的内部运行组件,用于保存整个k8s集群的信息。(节点信息、节点上部署的容器信息等)
  • api-server:提供对k8s集群进行资源操作的唯一入口,对于每一个请求都有校验和鉴权。
  • controller manager:负责管理controller,它们共同负责维护k8s集群的状态,如故障检测、容器滚动更新、自动伸缩等。deployment controller就是其中之一。
  • scheduler:负责资源调度,按照预定的策略将Pod分配到Node机器上。
  • kuberlet:是K8s在目标Node上的进程,用于处理master节点下发到本节点的任务,管理Pod以及Pod中的容器。 每个kubelet进程会在API Server上注册节点信息,定期向master节点汇报节点资源的使用情况,并通过cAdvisor监控容器和节点的资源。
  • kubectl:k8s的命令行接口,用于对k8s集群运行命令,与api-server交互。

k8s 的系统架构

从系统架构来看,k8s包含两类角色:管理节点和工作节点。