Kubernetes Cookbook 编程指南 中文版教程
创建时间:2018-12-07  访问量:3668  7  0

Kubernetes Cookbook 编程指南 中文版教程

在前面的章节中,你学习到了如何使用Kubernetes服务来在内部和外部转发容器端口。现在,是时候来深入了解一下它是如何工作的了。

在Kubernetes中有4个网络模型,我们将在以下章节中浏览这些细节:

  • 容器间的通信

  • Pod间的通信

  • Pod与服务间的通信

  • 外部与内部的通信

开始

本节,为了演示这4个模型是如何工作的,我们将运行两个nginx web应用。Docker Hub中的Nginx黙认端口是80。我们先通过修改nginx配置文件和Dockerfile将端口从80改为8080,然后创建另一个nginx Docker镜像。以下步骤将向你展示了如何从零构建,但是您也可以跳过它,使用我们预先构建的映像(https://hub.docker.com/r/msfuko/nginx_8800 )。

让我们先创建一个简单的Nginx配置文件。注意我们需要监听8800端口:

// create one nginx config file
# cat nginx.conf
server {
    listen 8800;
    server_name localhost;
    
    #charset koi8-r;
    
    #access_log /var/log/nginx/log/host.access.log main;
   
    location / {
        root /usr/share/nginx/html;
        index index.html index.htm;
    }
    
    #error_page 404 /404.html;
    #error_page 404 /404.html;
    # redirect server error pages to the static page /50x.html
    #
    
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
     	root /usr/share/nginx/html;
    }
}

接下来,我们需要改变黙认的nginx Dockerfile将暴露的端口80改为8800:

// modifying Dockerfile as expose 8800 and add config file inside
# cat Dockerfile
FROM debian:jessie
MAINTAINER NGINX Docker Maintainers "docker-maint@nginx.com"

RUN apt-key adv --keyserver hkp://pgp.mit.edu:80 --recv-keys
573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62
RUN echo "deb http://nginx.org/packages/mainline/debian/ jessie nginx" >>
/etc/apt/sources.list

ENV NGINX_VERSION 1.9.9-1~jessie

RUN apt-get update && \
apt-get install -y ca-certificates nginx=${NGINX_VERSION} && \
rm -rf /var/lib/apt/lists/*
COPY nginx.conf /etc/nginx/conf.d/default.conf

# forward request and error logs to docker log collector
RUN ln -sf /dev/stdout /var/log/nginx/access.log
RUN ln -sf /dev/stderr /var/log/nginx/error.log

VOLUME ["/var/cache/nginx"]

EXPOSE 8800

CMD ["nginx", "-g", "daemon off;"]

然后,我们需要使用Docker命令来构建它:

// build docker image
# docker build -t $(YOUR_DOCKERHUB_ACCOUNT)/nginx_8800 .

最后,我们可以将这个镜像放到Docker Hub 仓库:

// be sure to login via `docker login` first
# docker push $(YOUR_DOCKERHUB_ACCOUNT)/nginx_8800

执行完成后,你应该能够通过纯Docker命令运行这个容器:docker run -d -p 8800:8800 msfuko/nginx_8800。使用curl $IP:8800,你应该能够看到nginx的欢迎页面。

如何查看我的$IP

如果你运行在Linux系统上,那么ifconfig就可以帮你指出$IP的值。如果你运行其它平台的Docker机器上,docker-machine ip可以帮助指出$IP的值 。

如何去做...

Pod包含一个或多个容器,并运行在同一个主机上。每个Pod都有它们自己的IP地址;相同主机上的Pod中的所有容器彼此都是可见的。Pod中的容器几乎是同时创建、部署和删除的。

容器间的通信

我们将在一个Pod中创建两个Nginx容器,分另监听80和8800端口。在下面的配置文件中,你应该将第二个镜像的路径改为刚刚构建上传的镜像路径。

// create 2 containers inside one pod
# cat nginxpod.yaml
apiVersion: v1
kind: Pod
metadata:
	name: nginxpod
spec:
    containers:
        -
            name: nginx80
            image: nginx
            ports:
            -
                containerPort: 80
                hostPort: 80
        -
        name: nginx8800
        image: msfuko/nginx_8800
        ports:
            -
                containerPort: 8800
                hostPort: 8800

// create the pod
# kubectl create -f nginxpod.yaml
pod "nginxpod" created

在镜象拉取并运行后,使用kubectl命令我们可以看到它的状态变成了运行:

// list nginxpod pod
# kubectl get pods nginxpod
NAME READY STATUS RESTARTS AGE
nginxpod 2/2 Running 0 12m

我们可以发现REDAY列的数量变成了2/2,因为Pod中有两个容器。使用kubectl describe命令,我们可以看到Pod的详细信息:

// show the details of nginxpod
# kubectl describe pod nginxpod
Name: nginxpod
Namespace: default
Image(s): nginx,msfuko/nginx_8800
Node: kube-node1/10.96.219.33
Start Time: Sun, 24 Jan 2016 10:10:01 +0000
Labels: <none>
Status: Running
Reason:
Message:
IP: 192.168.55.5
Replication Controllers: <none>
Containers:
nginx80:
Container ID: docker://3b467d8772f09c57d0ad85caa66b8379799f3a60da055
d7d8d362aee48dfa832
Image: nginx
...
nginx8800:
Container ID: docker://80a77983f6e15568db47bd58319fad6d22a330c1c4c92
63bca9004b80ecb6c5f
Image: msfuko/nginx_8800
...

我们可以看到Pod运行kube-node1上,并且Pod的IP是192.168.55.5。

让我们登录到kube-node1中去观察这两个容器:

// list containers
# docker ps
CONTAINER ID IMAGE
COMMAND CREATED STATUS POR
TS NAMES
80a77983f6e1 msfuko/nginx_8800
"nginx -g 'daemon off" 32 minutes ago Up 32 minutes
k8s_nginx8800.645004b9_nginxpod_default_a08ed7cb-c282-11e5-9f21-
025a2f393327_9f85a41b
3b467d8772f0 nginx
"nginx -g 'daemon off" 32 minutes ago Up 32 minutes
k8s_nginx80.5098ff7f_nginxpod_default_a08ed7cb-c282-11e5-9f21-
025a2f393327_9922e484
71073c074a76 gcr.io/google_containers/pause:0.8.0
"/pause" 32 minutes ago Up 32 minutes
0.0.0.0:80->80/tcp, 0.0.0.0:8800->8800/tcp k8s_POD.5c2e23f2_nginxpod_
default_a08ed7cb-c282-11e5-9f21-025a2f393327_77e79a63

我们知道创建的这两个容器的ID是3b467d8772f0 和80a77983f6e1 。

我们将使用jq作为JSON解析器来减少冗余信息。对于jq的安装,简单的从https://stedolan.github.io/jq/download 下载二进制文件即可:

// inspect the nginx container and use jq to parse it
# docker inspect 3b467d8772f0 | jq '.[]| {NetworkMode: .HostConfig.
NetworkMode, NetworkSettings: .NetworkSettings}'
{
    "NetworkMode": "container:71073c074a761a33323bb6601081d44a79ba7de3dd59345fc33a36b00bca613f",
    "NetworkSettings": {
        "Bridge": "",
        "SandboxID": "",
        "HairpinMode": false,
        "LinkLocalIPv6Address": "",
        "LinkLocalIPv6PrefixLen": 0,
        "Ports": null,
        "SandboxKey": "",
        "SecondaryIPAddresses": null,
        "SecondaryIPv6Addresses": null,
        "EndpointID": "",
        "Gateway": "",
        "GlobalIPv6Address": "",
        "GlobalIPv6PrefixLen": 0,
        "IPAddress": "",
        "IPPrefixLen": 0,
        "IPv6Gateway": "",
        "MacAddress": "",
        "Networks": null
    }
}

我们可以看到网络模式被设置为映射容器模式。网络桥容器是:container:71073c074a761a33323bb6601081d44a79ba7de3dd59345fc33a36b00bca613f 。

让我们看一下关于nginx_8800容器的另一个配置:

// inspect nginx_8800
# docker inspect 80a77983f6e1 | jq '.[]| {NetworkMode: .HostConfig.
NetworkMode, NetworkSettings: .NetworkSettings}'
{
    "NetworkMode": "container:71073c074a761a33323bb6601081d44a79ba7de3dd59345fc33a36b00bca613f",
    "NetworkSettings": {
        "Bridge": "",
        "SandboxID": "",
        "HairpinMode": false,
        "LinkLocalIPv6Address": "",
        "LinkLocalIPv6PrefixLen": 0,
        "Ports": null,
        "SandboxKey": "",
        "SecondaryIPAddresses": null,
        "SecondaryIPv6Addresses": null,
        "EndpointID": "",
        "Gateway": "",
        "GlobalIPv6Address": "",
        "GlobalIPv6PrefixLen": 0,
        "IPAddress": "",
        "IPPrefixLen": 0,
        "IPv6Gateway": "",
        "MacAddress": "",
        "Networks": null
    }
}

网络模式也是设置在这个71073c074a761a33323bb6601081d44a79ba7de3dd59345fc33a36b00bca613 的容器中:

这是什么容器?我们发现这个网络容器由Kubernetes在启劝Pod的时间创建的。容器的名称是gcr.io/google_containers/pause:

// inspect network container `pause`
# docker inspect 71073c074a76 | jq '.[]| {NetworkMode: .HostConfig.NetworkMode, NetworkSettings: .NetworkSettings}'
{
	"NetworkMode": "default",
	"NetworkSettings": {
		"Bridge": "",
		"SandboxID": "59734bbe4e58b0edfc92db81ecda79c4f475f6c8433e17951e9c9047c69484e8",
		"HairpinMode": false,
		"LinkLocalIPv6Address": "",
		"LinkLocalIPv6PrefixLen": 0,
		"Ports": {
			"80/tcp": [{
				"HostIp": "0.0.0.0",
				"HostPort": "80"
			}],
			"8800/tcp": [{
				"HostIp": "0.0.0.0",
				"HostPort": "8800"
			}]
		},
		"SandboxKey": "/var/run/docker/netns/59734bbe4e58",
		"SecondaryIPAddresses": null,
		"SecondaryIPv6Addresses": null,
		"EndpointID": "d488fa8d5ee7d53d939eadda106e97ff01783f0e9dc9e4625d9e69500e1fa451",
		"Gateway": "192.168.55.1",
		"GlobalIPv6Address": "",
		"GlobalIPv6PrefixLen": 0,
		"IPAddress": "192.168.55.5",
		"IPPrefixLen": 24,
		"IPv6Gateway": "",
		"MacAddress": "02:42:c0:a8:37:05",
		"Networks": {
			"bridge": {
				"EndpointID": "d488fa8d5ee7d53d939eadda106e97ff01783f0e9dc9e4625d9e69500e1fa451",
				"Gateway": "192.168.55.1",
				"IPAddress": "192.168.55.5",
				"IPPrefixLen": 24,
				"IPv6Gateway": "",
				"GlobalIPv6Address": "",
				"GlobalIPv6PrefixLen": 0,
				"MacAddress": "02:42:c0:a8:37:05"
			}
		}
	}
}

我们可以发现网络模式被设置为default并且它的IP地址被设置为Pod的IP地址192.168.55.5;网关被设置为Node的docker0。路由说明如下图所示。当一个Pod被创建时,网络容器Pause将被创建,它被用来处理Pod网络的路由。然后,两个容器将与Pause共享网络接口;这就是为什么他们将彼此视为localhost:

Pod间通信

因为每个Pod都有它自己的IP地址,它使用Pod之间的通信很容器。在前面一章中,我们使用flannel作为层叠网络,这将要为每个Node定义同的网段。每个数据包都被封装为UDP数据包,因此,每个Pod的IP都是可路由的。数据包从Pod1通过veth(虚拟网络接口,与docker0连接)设备路由到flannel0。流量通过flanneld封装并发送到目标Pod所在的主机(10.42.1.172)上:

Pod与服务间的通信

Pod可以意外停止,因此,Pod的IP也可以改变。当我们暴露一个Pod或一个副本控制器的端口时,我们创建一个Kubernetes服务作为代理和负载均衡。Kubernetes将创建一个虚拟IP,它会接收客户端请求并代理发送到服务中的Pod。让我们回忆一个如何来做。首先,我们创建一个名为 my-first-nginx的副本控制器:

// create a rc named my-first-nginx
# kubectl run my-first-nginx --image=nginx --replicas=2 --port=80
replicationcontroller "my-first-nginx" created

然后,让我们列出Pod以确保这两个Pod都是通过这个rc创建的:

// two pods will be launched by this rc
# kubectl get pod
NAME READY STATUS RESTARTS AGE
my-first-nginx-hsdmz 1/1 Running 0 17s
my-first-nginx-xjtxq 1/1 Running 0 17s

接下来,让我们为Pod暴露80端口。这是nginx应用的黙认端口:

// expose port 80 for my-first-nginx
# kubectl expose rc my-first-nginx --port=80
service "my-first-nginx" exposed

使用describe描述服务详细信息。我们创建的服务类型是一个ClusterIP:

// check the details of the service
# kubectl describe service my-first-nginx
Name: my-first-nginx
Namespace: default
Labels: run=my-first-nginx
Selector: run=my-first-nginx
Type: ClusterIP
IP: 192.168.235.209
Port: <unnamed> 80/TCP
Endpoints: 192.168.20.2:80,192.168.55.5:80
Session Affinity: None
No events.

服务的虚拟IP是192.168.235.209,并暴露80端口。服务会将流量分发到192.168.20.2:80和192.168.55.5:80这两个端点上。说明如下所示:

kube-proxy是一个在每个Node节点充当服务代理的守程进程。它帮助映射服务设置,如每个节点上的IP和端口。它将创建符合iptables的规则:

// list iptables rule by filtering my-first-nginx
# iptables -t nat -L | grep my-first-nginx
REDIRECT tcp -- anywhere 192.168.235.209 /* default/
my-first-nginx: */ tcp dpt:http redir ports 43321
DNAT tcp -- anywhere 192.168.235.209 /* default/
my-first-nginx: */ tcp dpt:http to:10.96.219.33:43321

这两个规则属于KUBE-PORTALS-CONTAINER和KUBE-PORTALS-HOST链,它代表以80端口的虚拟IP的任何流量目的地都将被转到本机的43321端口上,无论流量来自容器还是主机。kube-proxy代理程序iptables规则使用Pod与服务之间的通信成为可能。你应该能在集群内部的目标Node或$nodeIP:43321上访问localhost:43321。

在程序中使用Kubernetes服务的环境变量

有时候,你需要在你的Kubernetes集群的程序中访问。你可以使用环境变量或DNS来访问它。环境变量是天然支持的最简单的方式。当一个服务创建时,kubelet将将服务中添加一组环境变量:

  • $SVCNAME_SERVICE_HOST

  • $SVCNAME_SERVICE_PORT

这里,$SVNNAME都是大写并且披折号被转换为下划线。Pod相要访问的服务必须在Pod创建之前创建,否则,环境变量不会被发布。例如,my-first-nginx产生的环境变量是:

  • MY_FIRST_NGINX_PORT_80_TCP_PROTO=tcp

  • MY_FIRST_NGINX_SERVICE_HOST=192.168.235.209

  • MY_FIRST_NGINX_SERVICE_PORT=80

  • MY_FIRST_NGINX_PORT_80_TCP_ADDR=192.168.235.209

  • MY_FIRST_NGINX_PORT=tcp://192.168.235.209:80

  • MY_FIRST_NGINX_PORT_80_TCP_PORT=80

  • MY_FIRST_NGINX_PORT_80_TCP=tcp://192.168.235.209:80

外部到内部之间的通信

external-to-internal的通信可以被设置为使用外部负载均衡,如GCE的转发规则或AWS的ELB,或通过直接访问Node节点的IP。这里,我们将介绍如何能够访问Node的IP。首先,我们运行一个具有两2上副本的名为my-second-nginx的副本控制器:

// create a rc with two replicas
# kubectl run my-second-nginx --image=nginx --replicas=2 --port=80

接下来,我们使用LoadBalancer暴露服务的80端口。正如我们在服务这一节讨论的,LoadBalancer也暴露一个Node端口:

// expose port 80 for my-second-nginx rc with type LoadBalancer
# kubectl expose rc my-second-nginx --port=80 --type=LoadBalancer

我们现在可以验证my-second-nginx服务详细信息。它有一个虚拟IP为192.168.156.93,端口80。它还有一个节点端口为31150:

// list the details of service
# kubectl describe service my-second-nginx
Name: my-second-nginx
Namespace: default
Labels: run=my-second-nginx
Selector: run=my-second-nginx
Type: LoadBalancer
IP: 192.168.156.93
Port: <unnamed> 80/TCP
NodePort: <unnamed> 31150/TCP
Endpoints: 192.168.20.3:80,192.168.55.6:80
Session Affinity: None
No events.

让我们列出iptables规则来看下不同服务类型之间的不同:

// list iptables rules and filtering my-seconde-nginx on node1
# iptables -t nat -L | grep my-second-nginx
REDIRECT tcp -- anywhere anywhere /* default/
my-second-nginx: */ tcp dpt:31150 redir ports 50563
DNAT tcp -- anywhere anywhere /* default/
my-second-nginx: */ tcp dpt:31150 to:10.96.219.141:50563
REDIRECT tcp -- anywhere 192.168.156.93 /* default/
my-second-nginx: */ tcp dpt:http redir ports 50563
DNAT tcp -- anywhere 192.168.156.93 /* default/
my-second-nginx: */ tcp dpt:http to:10.96.219.141:50563

// list iptables rules and filtering my-second-nginx on node2
# iptables -t nat -L | grep my-second-nginx
REDIRECT tcp -- anywhere anywhere /* default/
my-second-nginx: */ tcp dpt:31150 redir ports 53688
DNAT tcp -- anywhere anywhere /* default/
my-second-nginx: */ tcp dpt:31150 to:10.96.219.33:53688
REDIRECT tcp -- anywhere 192.168.156.93 /* default/
my-second-nginx: */ tcp dpt:http redir ports 53688
DNAT tcp -- anywhere 192.168.156.93 /* default/
my-second-nginx: */ tcp dpt:http to:10.96.219.33:53688

我们现在有与my-second-nginx相关的4个规则。它们在KUBE-NODEPORT-CONTAINER,KUBE-NODEPORT-HOST,KUBE-PORTALS-CONTAINER和KUBE-PORTALS-HOST链下。因为我们在此示例中暴露了Node的端口,如果流量是从外部流入到端口为31150的Node节点,这个流量将被直接转发到本地或跨节点的目标Pod中。下面中路由的说明:

通过一个跨Node的负载均衡机制,从Node端口(x.x.x.x:31150)或ClusterIP(192.168.156.93:80)传过来流量将被重定向到目标Pod。端口50563和53688是由Kubernetes自动分配的。

还可以参考

Kubernetes是基本层叠网络来转发端口的。在本章,我们也运行了Nginx的Pod和服务。回顾前面的章节将有助于你理解更多关于如果操作它。也可以参见以下章节:

  • 第1章构建你自己的容器,创建层叠网络、在Kubernetes中运行你的第一个容器这几节

  • 第2章理解Kubernetes相关概念,Pod的使用、Service的服务。