36661ffc8d4d9b79a0994a32820d7167.png

序言

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

kind: Pod

metadata:

name: my-app-v1

spec:

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 中,只需要两种结构类型就行了:

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

Maps

首先我们来看看 Maps,我们都知道 Map 是字典,就是一个

key:value

的键值对,Maps 可以让我们更加方便的去书写配置信息,例如:

---

apiVersion: v1

kind: Pod

第一行的

---

是分隔符,是可选的,在单一文件中,可用连续三个连字号

---

区分多个文件。这里我们可以看到,我们有两个键:

kind

apiVersion

,他们对应的值分别是:v1 和Pod。上面的 YAML 文件转换成 JSON 格式的话,你肯定就容易明白了:

{

"apiVersion": "v1",

"kind": "pod"

}

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

---

apiVersion: v1

kind: Pod

metadata:

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: v1

kind: Pod

metadata:

name: kube100-site

labels:

app: web

spec:

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: v1

kind: Pod

metadata:

name: my-app-v2

spec:

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 data

vi index.php

# index.php内容如下

echo "this is php";

vi index.html

# 内容如下

# 创建默认的default.conf文件

vi default.conf

# 内容如下 注意我们这里fastcgi_pass用了localhost

server {

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 ps

docker cp ./index.php [nginx容器的名称]:/usr/share/nginx/html

docker 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/html

ls

# 我们发现了我们刚上传到此目录的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: v1

kind: Pod

metadata:

name: my-app-v3

spec:

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来挂载配置文件吗?

Logo

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

更多推荐