【云原生--K8S】K8S部署一个C/S架构的服务器应用(重点讲Service)
K8s 中Service提供三种模式访问服务:ClusterIP、NodePort、LoadBalancer,其中ClusterIP仅支持内部访问,NodePort和LoadBalancer支持外部访问。而三种模式之间是包含关系:NodePort模式包含ClusterIP模式,LoadBalancer模式又包含NodePort模式。
文章目录
前言
在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地址。
更多推荐
所有评论(0)