Hits

K8S 部署一个完整项目案例

K8S部署一个项目

整体概述

本项目主要是一个混合了 GO,NodeJS,Python 等语言的项目,灵感来自于 Say Thanks 项目。主要实现的功能有两个:

  1. 用户通过前端发送感谢消息
  2. 有个工作进程会持续的计算收到感谢消息的排行榜

项目代码可在 [Github]()上获得。 saythx 项目的基础架构如下图:

构建镜像

前端

前端框架使用 [Vue](),在生产部署时,需要先在 [Node JS]() 的环境下进行打包构建。包管理器使用的是 [Yarn]()。然后使用 [Nginx]() 提供服务,并进行反向代理,将请求正确的代理至后端。

FROM node:10.13 as builder

WORKDIR /app
COPY . /app

RUN yarn install \
        && yarn build

FROM nginx:1.15

COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /app/dist /usr/share/nginx/html/

EXPOSE 80

Nginx 的配置文件如下:

upstream backend-up {
    server saythx-backend:8080;
}
server {
    listen 80;
    server_name localhost;
    
    charset utf-8;
    
    location / {
        root /usr/share/nginx/html;
        try_files $uri $uri/ /index.html;
    }
    
    location ~ ^/(api) {
        proxy_pass http://backend-up;
    }
}

将API的请求反向代理到后端服务上,其余请求全部留给前端进行处理。

后端

后端是使用 [Golang]() 编写的API服务,对请求进行相应处理,并将数据存储至 [Redis]() 当中,依赖管理使用的是 [dep]()。由于Golang是编译型语言,编译完成后会生成一个二进制文件,为了让镜像尽可能小,所以Dockerfile和前端的差不多,都使用了 [多阶段构建]() 的特性。

FROM golang:1.11.1 as builder

WORKDIR /go/src/be
COPY . /go/src/be
RUN go get -u github.com/golang/dep/cmd/dep \
        && dep ensure \
        && go build
        
FROM debian:stretch-slim
COPY --from=builder /go/src/be/be /usr/bin/be
ENTRYPOINT ["/usr/local/be"]
EXPOSE 8080

注意这里会暴露出来后端服务所监听的端口

Work

Work端使用的是 [Python](),用于计算已经存储至Redis当中的数据,并生成排行榜。依赖使用 [pip]() 进行安装。

FROM python:3.7-slim

WORKDIR /app
COPY . /app
RUN pip install -r requirements.txt

ENTRYPOINT ["python", "work.py"]

构建发布

接下来,我们只要在对应项目目录中,执行 docker build [OPTION] PATH 即可。一般我们会使用 -t name:tag 的方式打开tag。

# 以下分别在各自模块自己的目录内
docker build -t liuzhiwang/saythx-be .
docker build -t liuzhiwang/saythx-fe .
docker build -t liuzhiwang/saythx-work .

需要注意的是,前端项目由于项目内包含开发时的 node_modules 等文件,需要注意添加 .dockerignore 文件,忽略一些非预期的文件。docker build原理参考 http://moelove.info/2018/09/04/Docker-%E6%B7%B1%E5%85%A5%E7%AF%87%E4%B9%8B-Build-%E5%8E%9F%E7%90%86/。当镜像构建完成后,将他们发布至镜像仓库。

docker push liuzhiwang/saythx-be
docker push liuzhiwang/saythx-fe
docker push liuzhiwang/saythx-work

容器编排 docker-compose

[docker compose]() 是一种较为简单的可进行容器编排的技术,需要创建一个配置文件,通常情况下为 docker-compose.yml 。在saythx项目的根目录下,如下所示创建配置文件。

version: '3'

services:
  saythx-frontend:
    build:
      context: fe/.
    image: liuzhiwang/saythx-fe
    ports:
      - "8088:80"
    depends_on:
      - saythx-backend
    networks:
      - saythx

  saythx-backend:
    build:
      context: be/.
    image: liuzhiwang/saythx-be
    depends_on:
      - saythx-redis
    networks:
      - saythx
    environment:
      - REDIS_HOST=saythx-redis
    
  saythx-work:
    build:
      context: work/.
    image: liuzhiwang/saythx-work
    depends_on:
      - saythx-redis
    networks:
      - saythx
    environment:
      - REDIS_HOST=saythx-redis
      - REDIS_PORT=6379

  saythx-redis:
    image: "redis:5"
    networks:
      - saythx

networks:
  saythx:

在根目录下执行 docker-compose up 即可启动该项目,在浏览器中访问 http://localhost:8088/ 即可看到项目的前端页面,如下图:

https://img.lg1024.com/blog/img/saythx_fe.png

打开另外终端,进入项目根目录内,执行 docker-compose ps 命令即可看到当前的服务情况。

https://img.lg1024.com/blog/img/docker-compose-ps.png

可以看到各组件均是 up 状态,相关端口也已经暴露出来。可在浏览器中直接访问体验。

编写配置文件部署到k8s

在 K8S 中进行部署或者说与 K8S 交互的方式主要有三种:

  • 命令式
  • 命令式对象配置
  • 声明式对象配置

kubectl run redis --image='redis:latest' 这种方式便是命令式,这种方式简单,但是可重用性很低。毕竟命令执行完了之后,其他人不知道到底发生了什么。

命令对象配置,主要是编写配置文件,但是通过 kubectl create 之类命令的方式进行操作

再有一种便是声明式对象配置,主要也是通过编写配置文件,但是通过 kubectl apply 之类的方式进行操作。与第二种命令式对象配置的区别主要在于:对对象的操作将会得到保留,但同时这种方式有时候也并不好进行调试。

如果已经写好了 docker-compose.yml 的配置文件,并且已经验证了其可用性,可以直接使用 Kompose 工具将 docker-compose.yml 的配置文件进行转换。

kompose convert -f docker-compose.yml

Namespace

appVersion: v1
kind: Namespace
metadata: 
    name: work

指定了 Namespace name为 work。然后进行部署。

kubectl apply -f namespace.yaml

Redis资源

从前面的 docker-compose.yml 中也能发现,saythx中各个组件,只有Redis是无任何依赖的,所以先对redis进行部署。

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: redis
  name: saythx-redis
  namespace: work
spec:
  selector:
    matchLabels:
      app: redis
  replicas: 1
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - image: redis:latest
        name: redis
        ports:
        - containerPort: 6379

上面是一个 Deployment 的配置文件,部分字段讲解如下:

  • apiVersion 指定API的版本号,当前我们使用的K8S中,Deployment 的版本号为 apps/v1,而在K8S 1.9之前使用的版本号为 apps/v1beta2,在1.8之前的版本为 extensions/v1beta1
  • kind 指定资源的类型,这里 deployment 说明是一次部署。
  • metadata 指定了资源的元信息,例如其中的 namenamespace分别表示资源名称和所归属的 Namespace
  • spec 指定资源的配置信息,例如 replicas 指定副本数当前为1,template.spec 则指定了 pod 中容器的配置信息,这里的 pod 中只部署了一个容器。

kubectl -n work get all 查看所有 namespace = work 的资源信息

kubectl apply -f redis-deployment.yaml // 部署redis-deployment

kubectl -nwork exec -it saythx-redis-c8d864668-s8d6k bash 进入pod内测试

Redis service

由于 Redis 是后端服务的依赖,我们将它作为 Service 暴露出来。

apiVersion: v1
kind: Service
metadata:
  labels:
    app: redis
  name: saythx-redis
  namespace: work
spec:
  ports:
  - protocol: TCP
    port: 6379
    targetPort: 6379
  selector:
    app: redis
  type: NodePort

上面是一个 Service配置文件,简单点说就是为了能有一个稳定的入口访问我们的应用服务或者一组 pod。通过 Service 可以很方便的实现服务发现和负载均衡。kubectl get service -o wide 可以查看所有service 的名称,类型,IP,端口及创建时间和选择器等。service 有如下4种类型:

  • ClusterIP 是K8S当前默认的 Service类型,将service暴露于一个仅集群内可访问的虚拟IP上。
  • NodePor 是通过在集群内所有 Node 上都绑定固定端口的方式将服务暴露出来,这样便可以通过 <NodeIP>:<NodePort>访问服务了。
  • LoadBalancer 是通过 Cloud Provider 创建一个外部的负载均衡器,将服务暴露出来,并且会自动创建外部负载均衡器路由请求所需的 NodePort 或者 ClusterIP
  • ExternalName 是通过将服务由DNS CNAME的方式转发到指定的域名上将服务暴露出来,这需要 kube-dns 1.7或更高版本的支持。

kubectl apply -f redis-service.yaml // 部署redis service

kubectl get svc -n work // 查询Namespace = work的所有service

后端服务

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: backend
  name: saythx-backend
  namespace: work
spec:
  selector:
    matchLabels:
      app: backend
  replicas: 1
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
      - env:
        - name: REDIS_HOST
          value: saythx-redis
        image: liuzhiwang/saythx-be
        name: backend
        ports:
        - containerPort: 8080

这配置文件可以看到通过环境变量的方式,将 REDIS_HOST 传递给了后端服务。

kubectl apply -f backend-deployment.yaml // 部署后端服务

kubectl -n work get all // 查询 namespace = work 的所有资源

后端Service

后端服务是前端项目的依赖,故而我们也将其作为 service 暴露出来

apiVersion: v1
kind: Service
metadata:
  labels:
    app: backend
  name: saythx-backend
  namespace: work
spec:
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 8080
  selector:
    app: backend
  type: NodePort

kubectl apply -f backend-service.yaml // 部署后端service

kubectl -n work get svc // 查询 namespace = workd 的所有service

同样使用 NodePort 将其暴露出来,进行本地测试:

curl -i http://127.0.0.1:32051/api/v1/list // 如果请求不到说明端口没暴露出来

kubectl port-forward -n work saythx-backend-7b9f85c6db-9mrpd 8888:8080 // 该命令可以将 pod 内 8080端口暴露到外面,8888访问。

前端

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: frontend
  name: saythx-frontend
  namespace: work
spec:
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - image: liuzhiwang/saythx-fe
        name: frontend
        ports:
        - containerPort: 80

前端 deployment 跟后端一样。需要注意的是后端 Service 暴露出来后才能进行前端的部署,因为前端镜像中 Nginx 的反向代理配置中会检查后端是否可达

kubectl apply -f frontend-deployment.yaml // 部署前端deployment

kubectl -n work get all // 查询 Namespace=work 的所有资源

前端service

apiVersion: v1
kind: Service
metadata:
  labels:
    app: frontend
  name: saythx-frontend
  namespace: work
spec:
  ports:
  - name: "80"
    port: 80
    targetPort: 80
  selector:
    app: frontend
  type: NodePort

kubectl apply -f frontend-service.yaml // 部署 前端deployment

kubectl -n work get svc // 查询所有service

Work

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: work
  name: saythx-work
  namespace: work
spec:
  selector:
    matchLabels:
      app: work
  replicas: 1
  template:
    metadata:
      labels:
        app: work
    spec:
      containers:
      - env:
        - name: REDIS_HOST
          value: saythx-redis
        - name: REDIS_PORT
          value: "6379"
        image: liuzhiwang/saythx-work
        name: work

kubectl apply -f work-deployment.yaml // 部署work deployment

kubectl -nwork get all // 查询所有资源

到此,所有资源已部署完毕,并可以直接通过Node端口访问。http://127.0.0.1:32682 即可访问前端页面。

扩缩容

kubectl -n work scale --replicas=2 deployment/saythx-redis // 设置资源副本为2个。缩容跟这一样。

或者修改yaml文件中的 spec.replicas 为预期的个数,然后执行 kubectl apply -f work-deployment.yaml

本文链接:参与评论 »

--EOF--

专题「docker相关知识学习」的其它文章 »

Comments