序言

在大部分的k8s教程中,都是先讲k8s的基本理论,对于刚刚接触k8s的同学来说,可能一脸懵逼,望而生畏,所以我带领大家一步步动手部署,在部署的过程中一步步教大家理解其中的概念,让更多的技术人员迈入k8s的大门。

还没有安装k8s环境的可以参考我之前发布的文章:

《从零开始,带你一步步如何用kubeadm安装k8s》

工作目录:

/data:工作的主目录

/data/yaml:放置yaml文件

使用到的镜像:

nginx:1.15.11

php:7.2-fpm

一、让我们认识k8s的Pod

1.1 部署第一个pod

在大部分web应用中都离不开nginx,所以我们先从部署一个nginx开始我们的k8s奇妙之旅。

新建yaml文件

vi app-v1.yaml

 apiVersion: v1kind: Podmetadata:   name: my-app-v1spec:   containers:     - name: my-nginx       image: nginx:1.15.11       ports:         - containerPort: 80

创建我们的第一个pod

 kubectl create -f app-v1.yaml

查看我们刚刚建立的pod

 kubectl get pod

接下来让我们解析下上述yaml文件的含义:

  • apiVersion,指定api版本,我们可以通过kubectl api-versions命令从我们的k8s集群中获取

  • kind,资源类型,这里我们创建的是一个Pod资源,这里资源类型可以是 Deployment、Job、Ingress、Service、ConfigMap 等(我们后续都会讲解)

  • metadata:包含了我们定义的 Pod 的一些 meta 信息,比如名称、namespace、标签等等

  • spec:包括一些 containers,storage,volumes,或者其他 Kubernetes 需要知道的参数,以及诸如是否在容器失败时重新启动容器的属性

请大家记住一个完整的yaml文件必须包含apiVersion、kind、metadata、spec这四个元素。

1.2.1 YAML文件的格式

它的基本语法规则如下:

  • 大小写敏感

  • 使用缩进表示层级关系

  • 缩进时不允许使用Tab键,只允许使用空格。

  • 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可

  • # 表示注释,从这个字符一直到行尾,都会被解析器忽略。

在我们的 kubernetes 中,只需要两种结构类型就行了:

  • Lists

  • Maps

也就是说,你可能会遇到 Lists 的 Maps 和 Maps 的 Lists,等等。不过不用担心,你只要掌握了这两种结构也就可以了,其他更加复杂的我们暂不讨论。

Maps

首先我们来看看 Maps,我们都知道 Map 是字典,就是一个key:value的键值对,Maps 可以让我们更加方便的去书写配置信息,例如:

 ---apiVersion: v1kind: Pod

第一行的---是分隔符,是可选的,在单一文件中,可用连续三个连字号---区分多个文件。这里我们可以看到,我们有两个键:kindapiVersion,他们对应的值分别是:v1 和Pod。上面的 YAML 文件转换成 JSON 格式的话,你肯定就容易明白了:

 {    "apiVersion": "v1",    "kind": "pod"}

我们再创建一个相对复杂一点的 YAML 文件,创建一个 KEY 对应的值不是字符串而是一个 Maps:

 ---apiVersion: v1kind: Podmetadata: name: kube100-site labels:   app: web

上面的 YAML 文件,metadata 这个 KEY 对应的值就是一个 Maps 了,而且嵌套的 labels 这个 KEY 的值又是一个 Map,你可以根据你自己的情况进行多层嵌套。

上面我们也提到了 YAML 文件的语法规则,YAML 处理器是根据行缩进来知道内容之间的关联性的。比如我们上面的 YAML 文件,使用了两个空格作为缩进,空格的数量并不重要,但是得保持一致,并且至少要求一个空格(什么意思?就是你别一会缩进两个空格,一会缩进4个空格)。

我们可以看到 name 和 labels 是相同级别的缩进,所以 YAML 处理器就知道了他们属于同一个 MAP,而 app 是 labels 的值是因为 app 的缩进更大。

注意:在 YAML 文件中绝对不要使用 tab 键。

同样的,我们可以将上面的 YAML 文件转换成 JSON 文件:

 {  "apiVersion": "v1",  "kind": "Pod",  "metadata": {    "name": "kube100-site",    "labels": {      "app": "web"   } }}

或许你对上面的 JSON 文件更熟悉,但是不得不承认 YAML 文件的语义化程度更高

Lists

Lists 就是列表,说白了就是数组,在 YAML 文件中我们可以这样定义:

 args - Cat - Dog - Fish

可以有任何数量的项在列表中,每个项的定义以破折号(-)开头的,与父元素直接可以缩进一个空格。对应的 JSON 格式如下:

 {    "args": ["Cat", "Dog", "Fish"]}

当然,list 的子项也可以是 Maps,Maps 的子项也可以是list,如下所示:

 ---apiVersion: v1kind: Podmetadata: name: kube100-site labels:   app: webspec: containers:   - name: front-end     image: nginx     ports:       - containerPort: 80   - name: flaskapp-demo     image: jcdemo/flaskapp     ports:       - containerPort: 5000

比如这个 YAML 文件,我们定义了一个叫 containers 的 List 对象,每个子项都由 name、image、ports 组成,每个 ports 都有一个 key 为 containerPort 的 Map 组成,同样的,我们可以转成如下 JSON 格式文件:

 {    "apiVersion": "v1",    "kind": "Pod",    "metadata": {        "name": "kube100-site",        "labels": {            "app": "web"       }   },    "spec": {        "containers": [{            "name": "front-end",            "image": "nginx",            "ports": [{                "containerPort": 8080           }]       }, {            "name": "flaskapp-demo",            "image": "jcdemo/flaskapp",            "ports": [{                "containerPort": 5000           }]       }]   }}

是不是觉得用 JSON 格式的话文件明显比 YAML 文件更复杂了呢?

1.2.2 真正理解k8s中的pod

2.1 pod的概念

pod是k8s中最重要的概念,所以我们一定要先理解pod的概念。

pod是k8s中调度的最小单元,我们要清楚pod是一个逻辑上的概念,并不存在一个所谓的Pod隔离环境,也就是说,K8s 真正处理的还是宿主机操作系统上 Linux 容器的 Namespace 和 Cgroups,Pod其实是一组共享了某些资源的容器。具体的说:Pod 里的所有容器,共享的是同一个Network Namespace,并且可以声明共享同一个Volume。

2.2 pod共享网络

比如说现在有一个 Pod,其中包含了一个容器 A 和一个容器 B,它们两个就要共享 Network Namespace。在 Kubernetes 里的解法是这样的:它会在每个 Pod 里,额外起一个 Infra container 小容器来共享整个 Pod 的 Network Namespace。

Docker的4种网络模式中有一种模式是container模式,它能够让很多容器共享一个网络名称空间, 具体的原理是先使用briage模式启动第一个容器, 之后启动的其他容器纷纷使用container模式将网络环境绑定到这第一个容器上。这样这些容器的网络就连接到了一起,他们互相可以使用localhost这种方式进行网络通信。

Infra container 是一个非常小的镜像,大概 100~200KB 左右,是一个汇编语言写的、永远处于“暂停”状态的容器。由于有了这样一个 Infra container 之后,其他所有容器都会通过 Join Namespace 的方式加入到 Infra container 的 Network Namespace 中。

所以说一个 Pod 里面的所有容器,它们看到的网络视图是完全一样的。即:它们看到的网络设备、IP地址、Mac地址等等,跟网络相关的信息,其实全是一份,这一份都来自于 Pod 第一次创建的这个 Infra container,这就是 Pod 解决网络共享的一个解法。

在 Pod 里面,一定有一个 IP 地址,是这个 Pod 的 Network Namespace 对应的地址,也是这个 Infra container 的 IP 地址。所以大家看到的都是一份,而其他所有网络资源,都是一个 Pod 一份,并且被 Pod 中的所有容器共享,这就是 Pod 的网络实现方式。

由于需要有一个相当于说中间的容器存在,所以整个 Pod 里面,必然是 Infra container 第一个启动,并且整个 Pod 的生命周期是等同于 Infra container 的生命周期的,与容器 A 和 B 是无关的。这也是为什么在 Kubernetes 里面,它是允许去单独更新 Pod 里的某一个镜像的,即:做这个操作,整个 Pod 不会重建,也不会重启,这是非常重要的一个设计。

注意:这种共享网络机制,可以让同一个pod中的容器访问使用localhost,同时也限制了同一个pod中的容器端口不能重复。

2.3 pod共享存储

Pod 共享存储就相对比较简单,现在有两个容器,一个是 Nginx,另外一个是php容器,在 Nginx 里放一些文件,让我能通过 Nginx 访问到,如果访问的是php文件,PHP-FastCGI也要能够访问到,所以我们需要去 share 这个目录,我 share 文件或者是 share 目录在 Pod 里面是非常简单的,实际上就是把 volume 变成了 Pod level。然后所有容器,就是所有同属于一个 Pod 的容器,他们共享所有的 volume。

1.2 部署Nginx+PHP

我们改造一下我们的第一个pod,验证一下pod中的容器是共享网络的和如何实现共享存储。

新建yaml文件

vi app-v2.yaml

 apiVersion: v1kind: Podmetadata:   name: my-app-v2spec:   containers:     - name: my-nginx       image: nginx:1.15.11       ports:         - containerPort: 8080       volumeMounts:         - name: app-www           mountPath: /usr/share/nginx/html     - name: my-php       image: php:7.2-fpm       ports:         - containerPort: 9000       volumeMounts:         - name: app-www           mountPath: /var/www/html   volumes:     - name: app-www       emptyDir: {}

创建我们新的pod

 kubectl create -f app-v2.yaml

查看我们新建的pod的ip地址和所在的节点

 kubectl get pod -o wide

在pod所在的节点创建测试的PHP文件和Nginx配置文件

 # 创建data文件夹mkdir datavi index.php# index.php内容如下<?php echo "this is php";vi index.html# 内容如下this is html# 创建默认的default.conf文件vi default.conf# 内容如下 注意我们这里fastcgi_pass用了localhostserver {   listen   80 default;   index index.php index.html index.htm;   server_name localhost;   root /usr/share/nginx/html;   location ~ \.php {       include fastcgi_params;       fastcgi_pass   localhost:9000;       fastcgi_index index.php;       fastcgi_param SCRIPT_FILENAME /var/www/html/$fastcgi_script_name;   }}

注意:上述fastcgi_pass的值是 localhost:9000

把测试的文件拷贝到容器中

 # 查看nginx容器的名字docker psdocker cp ./index.php [nginx容器的名称]:/usr/share/nginx/htmldocker cp ./default.conf [nginx容器的名称]:/etc/nginx/conf.d

验证

 # 访问php文件curl my-app-v2容器所在pod的id:80/index.php# 访问成功,证明一个pod中的所有容器网络是共享的,可以用localhost访问到# 查看php容器中的/var/www/html是否有我们刚上传的index.php文件docker exec -it 你的php容器的名字 /bin/bash# 进入容器后执行cd /var/www/htmlls# 我们发现了我们刚上传到此目录的index.php文件,所以nginx的/usr/share/nginx/html目录和PHP的/var/www/html目录通过volume:emptyDir已经完成了共享

2.1 给pop挂载目录的方式

app-v2.yaml配置文件在最下面定义了一个叫“app-www”的volume,然后上面的两个容器来使用它就好了,其实volumes这个地方有三种选择

 # 方式1:volumes: - name: app-logs   emptyDir: {}  # 方式2:volumes: - name: app-logs   hostPth:     path: "/data"    # 方式3:   volumes: - name: app-logs   gcePersistenDisk:     pdName: my-data-disk //my-data-disk需要先创建好     fsType: ext4

emptyDir:是Pod分配到Node后创建的,他的初始内容为空,pod在Node上移除以后也就被销毁了。

hostPath:是挂载到宿主机上的目录,比较适用于需要永久保存的数据

gcePersistenDisk:表示使用谷歌公有云提供的磁盘创建my-data-disk:gcloud compute disks create --size=500GB --zone=us-centrall-a my-data-disk

我们在刚才验证的时候,是通过docker cp拷贝文件到容器中的,这种操作只是为了验证volume:emptyDir共享存储,接下来我们再来看看如何用hostPath挂载目录到宿主机上

把刚才的index.php和default.conf分别放到/data/www和/data/conf.d目录下面,然后分别scp到各个节点,因为我们创建pod的时候,不一定创建到哪个node上(后面会讲解指定node创建pod)。

在/data中新建yaml文件

vi app-v3.yaml

 apiVersion: v1kind: Podmetadata:   name: my-app-v3spec:   containers:     - name: my-nginx       image: nginx:1.15.11       ports:         - containerPort: 8080       volumeMounts:         - name: app-www           mountPath: /usr/share/nginx/html         - name: app-conf           mountPath: /etc/nginx/conf.d     - name: my-php       image: php:7.2-fpm       ports:         - containerPort: 9000       volumeMounts:         - name: app-www           mountPath: /var/www/html   volumes:     - name: app-www       hostPath:         path: /data/www     - name: app-conf       hostPath:         path: /data/conf.d

创建我们新的pod

 kubectl create -f app-v3.yaml

查看新pod的ip地址

 kubectl get pod -o wide

验证

 curl [my-app-v3容器所在pod的id]:80/index.php

到现在为止您是不是会有很多疑惑? 1、为什么用curl来验证而不是用浏览器直接访问验证? 2、如果node节点多了,每个pod容器都要用volumes:hostPath来挂载配置文件吗? 由于篇幅有限,这些疑问我会在下一章节中持续讲解,欢迎大家持续关注,也欢迎大家点赞和转发。

1a2b901a1068b6703efc3cd75aa45596.gif

推荐阅读: 推荐一款爱不释手的文本编辑器Typora 如何解决Redis的缓存穿透、缓存雪崩和缓存击穿 架构师必须了解的日志管理ELK 初识LVS负载均衡 API接口安全您做了哪些? 如何提高您的MySQL性能 Redis从入门到高可用分布式实战(一) 让我们一起开启Docker的大门 带您一小时玩转Docker
Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐