前言

在K8S中Deployment的作用是部署应用,管理Pod。但是在版本升级或者应用异常崩溃时Pod会销毁重新创建。这时Pod的IP地址会发生变化。而且部署应用时还可能有多个Pod,每个Pod的IP地址又不同。那么我们应该如何访问应用呢?
所以K8s 提供了Service,用来做服务发现与负载均衡。本文通过演示如何部署一个C/S架构的服务器应用来理解Service。


一、一个简单的C/S程序

服务端代码server.cpp:

#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <poll.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#include <iostream>
#include <vector>
#include <string>

#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)

typedef std::vector<struct pollfd> PollFdList;

int main(void)
{
	signal(SIGPIPE, SIG_IGN);
	signal(SIGCHLD, SIG_IGN);

	int listenfd;
	if ((listenfd = socket(PF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP)) < 0)
		ERR_EXIT("socket");

	struct sockaddr_in servaddr;
	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(5188);
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

	int on = 1;
	if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
		ERR_EXIT("setsockopt");

	if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
		ERR_EXIT("bind");
	if (listen(listenfd, SOMAXCONN) < 0)
		ERR_EXIT("listen");

	struct pollfd pfd;
	pfd.fd = listenfd;
	pfd.events = POLLIN;

	PollFdList pollfds;
	pollfds.push_back(pfd);

	int nready;

	struct sockaddr_in peeraddr;
	socklen_t peerlen;
	int connfd;
	
	while (1)
	{
		nready = poll(&*pollfds.begin(), pollfds.size(), -1);
		if (nready == -1)
		{
			if (errno == EINTR)
				continue;
			
			ERR_EXIT("poll");
		}
		if (nready == 0)	// nothing happended
			continue;
		
		if (pollfds[0].revents & POLLIN)
		{
			peerlen = sizeof(peeraddr);
			connfd = accept4(listenfd, (struct sockaddr*)&peeraddr,
						&peerlen, SOCK_NONBLOCK | SOCK_CLOEXEC);

			if (connfd == -1)
				ERR_EXIT("accept4");

			pfd.fd = connfd;
			pfd.events = POLLIN;
			pfd.revents = 0;
			pollfds.push_back(pfd);
			--nready;

			std::cout<<"ip="<<inet_ntoa(peeraddr.sin_addr)<<
				" port="<<ntohs(peeraddr.sin_port)<<std::endl;
			if (nready == 0)
				continue;
		}

		for (PollFdList::iterator it=pollfds.begin()+1;
			it != pollfds.end() && nready >0; ++it)
		{
				if (it->revents & POLLIN)
				{
					--nready;
					connfd = it->fd;
					char buf[128] = {0};
					int ret = read(connfd, buf, 128);
					if (ret == -1)
						ERR_EXIT("read");
					if (ret == 0)
					{
						std::cout<<"client close"<<std::endl;
						it = pollfds.erase(it);
						--it;

						close(connfd);
						continue;
					}

					printf("recv size = %d, str = %s\n", ret, buf);
					sleep(1);
					
					std::string str("hello world!");
					ret = write(connfd, str.c_str(), str.size());
					printf("send size = %d, str = %s\n", ret, str.c_str());
				}
		}
	}

	return 0;
}

客户端代码client.cpp:

#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string>
#include <iostream>

int main(int argc, char* argv[])
{
	int sclient = socket(AF_INET, SOCK_STREAM, 0);//IPPROTO_TCP
	if (sclient == -1)
	{
		std::cout << "invalid socket!" << std::endl;
		return 0;
	}

	sockaddr_in serAddr;
	serAddr.sin_family = AF_INET;
	serAddr.sin_port = htons(30060);
	inet_aton("192.168.99.104", &serAddr.sin_addr);
	if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == -1)
	{
		std::cout << "connect error!" << std::endl;		
		char c = getchar();
		close(sclient);
		return 0;
	}

	std::cout << "connect success!" << std::endl;	
	std::string str("hello, ni hao!\n");
	int ret = send(sclient, str.c_str(), str.size(), 0);
std::cout << "send size = " << ret << " str = " << str << std::endl;

	char buf[128] = {0};
	ret = recv(sclient, buf, 128, 0);
std::cout << "recv: size = " << ret << " str = " << buf << std::endl;	
	close(sclient);
	
	while(1);
	
	return 0;
}

二、打包镜像

1.打包服务端镜像

在c:/minikube目录下新建目录server,把server.cpp拷贝到这个目录,编写server-dockerfile文件如下:

FROM codenvy/cpp_gcc
RUN mkdir /home/user/myapp
ADD server.cpp /home/user/myapp
WORKDIR /home/user/myapp
RUN g++ server.cpp -o server
CMD ["./server"]

打包生成server:v1镜像文件:

docker build -f ./server-dockerfile -t server:v1 .

2.打包客户端镜像

在c:/minikube目录下新建目录client,把client.cpp拷贝到这个目录,编写client-dockerfile文件如下:

FROM codenvy/cpp_gcc
RUN mkdir /home/user/myapp
ADD client.cpp /home/user/myapp
WORKDIR /home/user/myapp
RUN g++ client.cpp -o client
CMD ["./client"]

打包生成client:v1镜像文件:

docker build -f ./client-dockerfile -t client:v1 .

三、创建Deployment

编写server-deployment.yaml文件如下:

apiVersion: apps/v1                 #版本
kind: Deployment                    #创建资源的类型
metadata:                           #资源的元数据
  name: server                      #资源的名称,是元数据必填项
spec:                               #期望状态
  replicas: 3                       #创建的副本数量(pod数量),不填默认为1
  selector:                         #选择器
    matchLabels:
      app: server
  template:                         #定义pod的模板
    metadata:                       #pod的元数据
      labels:                       #labels标签,必填一个
        app: server             
    spec:                           #pod的期望状态
      containers:                   #容器
      - name: server                #容器名称
        image: server:v1            #镜像
        ports:                      #容器的端口
        - containerPort: 5188

执行server-deployment.yaml文件创建deployment:

kubectl apply -f server-deployment.yaml

在这里插入图片描述

四、创建Service

创建Service后,删除Deployment后重新启动Deployment,或者升级server版本后,都可以使用Service的对应IP访问服务,不需要重新创建Service。

1.使用ClusterIP类型创建Service

编写server-service.yaml文件如下:

apiVersion: v1               #版本
kind: Service                #创建资源的类型
metadata:                    #资源的元数据
  name: server               #资源的名称,是元数据必填项
  labels:                    #labels标签  
    app: server
spec:                        #期望状态
  type: ClusterIP            #服务类型
  ports:                     #端口
    - port: 30061
      targetPort: 5188       #与containerPort一样
      protocol: TCP
  selector:
    app: server

执行server-service.yaml文件创建service:

kubectl apply -f server-service.yaml

在这里插入图片描述
此时只支持通过ClusterIP模式来访问server,client.cpp中修改服务端IP地址为虚拟IP:10.108.160.240,端口为port:30061

serAddr.sin_port = htons(30061);
inet_aton("10.108.160.240", &serAddr.sin_addr);

2.使用NodePort类型创建Service

修改server-service.yaml文件如下:

apiVersion: v1               #版本
kind: Service                #创建资源的类型
metadata:                    #资源的元数据
  name: server               #资源的名称,是元数据必填项
  labels:                    #labels标签  
    app: server
spec:                        #期望状态
  type: NodePort             #服务类型
  ports:                     #端口
    - port: 30061
      targetPort: 5188       #与containerPort一样
      protocol: TCP
      nodePort: 30060        #Node端口
  selector:
app: server

执行server-service.yaml文件创建service:
在这里插入图片描述
此时既可支持通过ClusterIP模式来访问server,client.cpp中修改服务端IP地址为虚拟IP:10.106.31.136,端口为port:30061

serAddr.sin_port = htons(30061);
inet_aton("10.106.31.136", &serAddr.sin_addr);

也可支持通过NodePort模式来访问server,client.cpp中修改服务端IP地址为minikube所在宿主机IP地址:192.168.99.104,端口为nodePort:30060

serAddr.sin_port = htons(30060);
inet_aton("192.168.99.104", &serAddr.sin_addr);

3.使用LoadBalancer类型创建Service

修改server-service.yaml文件如下:

apiVersion: v1               #版本
kind: Service                #创建资源的类型
metadata:                    #资源的元数据
  name: server               #资源的名称,是元数据必填项
  labels:                    #labels标签  
    app: server
spec:                        #期望状态
  type: LoadBalancer         #服务类型
  ports:                     #端口
    - port: 30061
      targetPort: 5188       #与containerPort一样
      protocol: TCP
      nodePort: 30060        #Node端口
  selector:
    app: server

执行server-service.yaml文件创建service:
在这里插入图片描述
由于minikube不支持LoadBalancer,所以EXTERNAL-IP为pending。导致无法在此环境下演示LoadBalancer模式访问。后续有机会再在K8S环境中演示。
此时仍可支持通过ClusterIP模式来访问server,client.cpp中修改服务端IP地址为虚拟IP:10.97.105.244,端口为port:30061

serAddr.sin_port = htons(30061);
inet_aton("10.97.105.244", &serAddr.sin_addr);

也可支持通过NodePort模式来访问server,client.cpp中修改服务端IP地址为minikube所在宿主机IP地址:192.168.99.104,端口为nodePort:30060

serAddr.sin_port = htons(30060);
inet_aton("192.168.99.104", &serAddr.sin_addr);

4.执行client程序

创建deployment client执行client:v1镜像:

kubectl create deploy client --image=client:v1

执行命令查看client运行日志:

kubectl logs POD-NAME

在这里插入图片描述

五、删除Deployment和Service

执行以下命令删除deployment client

kubectl delete deploy client

执行以下命令删除deployment server:

kubectl delete -f ./server/server-deployment.yaml

执行以下命令删除Service:

kubectl delete -f ./server/server-service.yaml

总结

K8s 中Service提供三种模式访问服务:ClusterIP、NodePort、LoadBalancer,其中ClusterIP仅支持内部访问,NodePort和LoadBalancer支持外部访问。而三种模式之间是包含关系:NodePort模式包含ClusterIP模式,LoadBalancer模式又包含NodePort模式。同时Service还提供了负载均衡功能,应用代码不需要再考虑负载均衡。
Service与Deployment概念分开的好处是应用部署和服务发现分离,当应用重启、升级、重新部署的时候,访问的客户端不需要修改服务IP地址。

Logo

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

更多推荐