程序部署环境的容器化已经是大势所趋,微服务为容器化提供了广阔的应用舞台,k8s已经把Docker纳入为它的底层支撑容器引擎,一统江湖,成为了容器技术事实上的标准。一般的应用程序是不能直接拿来部署到容器上的,需要经过一些修改才能移植到k8s上。那么这些改动包括哪些内容呢?

它主要有两个部分:

  • 第一部分是服务调用。不论是微服务之间的调用,还是微服务调用数据库或前端调用后端,调用的方式都是一样的。都需要知道IP地址,端口和协议,例如“http://127.0.0.1:80”, 其中“http”是协议,“127.0.0.1”是IP地址,“80”是端口。它的关键是让k8s的配置文件和应用程序都共享相同的调用地址。
  • 第二部分是数据的持久存储。在程序运行时,经常要访问持久存储(硬盘)上的数据,例如日志,配置文件或临时共享数据。程序在容器中运行,一旦出现问题,容器会被摧毁,k8s会自动重新生成一个与原来一模一样的容器,并在上面重新部署应用程序。在集群环境下,用户感觉不到容器故障,因为系统已经自动修复了。但当容器被摧毁时,容器上的数据也一起被摧毁了,因此要保证程序运行的连续性,就要让持久存储不受容器故障的影响。

程序实例:

我们通过一个Go(别的语言也大同小异)微服务程序做例子来展示要做的修改。它本身的功能非常简单,只是用SQL语句访问数据库中的数据,并写入日志。你可以简单地把它分成两层,后端数据访问层和数据库层。在k8s中它被分成两个服务。一个是后端服务程序,另一个是数据库(用MySQL)服务。后端程序要调用数据库服务,然后会把一些数据写入日志,而且这个日志不能因为容器故障而丢失。数据库对数据的保存要求更高,即使k8s集群或虚拟机出了问题或断电也要保证数据的存在。

file

上面是程序的目录结构。我们重点讲一下与k8s相关的。“config”目录包含与程序配置有关的代码,“logs”目录是用来存储日志文件的,没有代码。“script”目录是重点,里面包含了所有与部署程序相关的文件。其中“database”子目录里面是数据库脚本,“kubernetes”子目录存有k8s的所有配置文件,一回儿还会详细讲解。

服务调用:

服务调用涉及到两个不同的部分。一部分是k8s的配置文件,它负责服务的注册和发现。所有部署在k8s上的应用都通过k8s的服务来进行互相调用。另一部分是应用程序,它需要通过k8s的服务来访问其他程序。在没有k8s时,后端要想访问数据库,代码是这样的:

db, err := sql.Open("mysql", "dbuser:dbuser@tcp(localhost:3306)/service_config?charset=utf8")

其中,“dbuser:dbuser”是数据库用户名和口令,“localhost:3306”是数据库主机名和端口地址,“service-config”是数据库名,共有五个数据需要读取。迁移到k8s之后,我们要把这些参数从程序中提取出来,转化成从k8s中读取相关数据。

k8s配置:

先来看一下k8s的配置文件。

file

上面就是k8s的配置文件目录结构,最外层(kubernetes目录下)有两个“yaml”文件“k8sdemo-config.yaml”和"k8sdemo-secret.yaml",它们是被不同服务共享的,因此放在最外层。另外还有一个"k8sdemo.sh"文件是k8s命令文件,用来创建k8s对象。“kubernetes”目录下有两个子目录“backend”和“database”分别存放后端程序和数据库的配置文件。它们内部的结构是类似的,都有三个“yaml”文件:

  • backend-deployment.yaml:部署配置文件,
  • backend-service.yaml:服务配置文件
  • backend-volume.yaml:持久卷配置文件.

关于k8s的核心概念,请参阅“通过实例快速掌握k8s(Kubernetes)核心概念”. “backend”目录还多了一个“docker”子目录用来存储backend应用的Docker镜像,database的镜像文件直接从Docker的库中取得,因此不需要另外生成镜像文件。

k8s参数配置:

要想集成应用程序和k8s需要两个层面的参数共享,一个是应用程序和k8s之间的参数共享,另一个是不同k8s服务之间的参数共享。

k8s共享参数定义:

共享参数可以通过两种方式实现,一个是环境变量,另一个是持久卷。这两种方式大同小异,我们这里用环境变量的方式。这其中最关键的是“k8sdemo-config.yaml”和"k8sdemo-secret.yaml"这两个文件,它们分别存储了普通参数和保密参数。这些参数是属于整个应用程序的,被各个服务共享。

下面就是“k8sdemo-config.yaml”,它里面(在“data:”下面)定义了三个数据库参数,分别是数据库主机(MYSQL_HOST),数据库端口(MYSQL_PORT),数据库名(MYSQL_DATABASE)。

apiVersion: v1 kind: ConfigMap metadata:   name: k8sdemo-config  # ConfigMap的名字, 在引用数据时需要   labels:     app: k8sdemo data:   MYSQL_HOST: k8sdemo-database-service   # 数据库主机   MYSQL_PORT: "3306" # 数据库端口   MYSQL_DATABASE: service_config # 数据库名

下面就是“k8sdemo-secret.yaml”,它里面(在“data:”下面)也定义了三个数据库参数,根用户口令(MYSQL_ROOT_PASSWORD),普通用户名(MYSQL_USER_NAME),普通用户口令(MYSQL_USER_PQSSWORD)

apiVersion: v1 kind: Secret metadata:   name: k8sdemo-secret   labels:     app: k8sdemo data:   MYSQL_ROOT_PASSWORD: cm9vdA== # 根用户口令("root")   MYSQL_USER_NAME: ZGJ1c2Vy # 普通用户名("dbuser")   MYSQL_USER_PASSWORD: ZGJ1c2Vy # 普通用户口令("dbuser") 

有关k8s的参数配置详细信息,请参阅“通过搭建MySQL掌握k8s(Kubernetes)重要概念(下):参数配置”.

引用k8s共享参数:

下面就是“backend-deployment.yaml”,它定义了“backend“服务的部署(Deployment)配置。它的“containers:”部分定义了容器,“env:”部分定义了环境变量,也就是我们所熟悉的操作系统的环境变量,一般是由系统来定义。不同的系统例如Linux和Windows都有自己的方法来定义环境变量。

apiVersion: apps/v1 kind: Deployment metadata:   name: k8sdemo-backend-deployment   labels:     app: k8sdemo-backend spec:   selector:     matchLabels:       app: k8sdemo-backend   strategy:     type: Recreate   template:     metadata:       labels:         app: k8sdemo-backend     spec:       containers: # 定义容器         - image: k8sdemo-backend-full:latest           name: k8sdemo-backend-container           imagePullPolicy: Never           env: # 定义环境变量             - name: MYSQL_USER_NAME               valueFrom:                 secretKeyRef:                   name: k8sdemo-secret                   key: MYSQL_USER_NAME             - name: MYSQL_USER_PASSWORD               valueFrom:                 secretKeyRef:                   name: k8sdemo-secret                   key: MYSQL_USER_PASSWORD             - name: MYSQL_HOST               valueFrom:                configMapKeyRef:                  name: k8sdemo-config                  key: MYSQL_HOST             - name: MYSQL_PORT               valueFrom:                 configMapKeyRef:                   name: k8sdemo-config                   key: MYSQL_PORT             - name: MYSQL_DATABASE               valueFrom:                 configMapKeyRef:                   name: k8sdemo-config                   key: MYSQL_DATABASE           ports:             - containerPort: 80               name: portname           volumeMounts:             - name: k8sdemo-backend-persistentstorage               mountPath: /app/logs       volumes:         - name: k8sdemo-backend-persistentstorage           persistentVolumeClaim:             claimName: k8sdemo-backend-pvclaim 

k8s的环境变量主要是用来向容器传递参数的。环境变量引用了“k8sdemo-config.yaml”和"k8sdemo-secret.yaml"文件里的参数,这样就在k8s内部用过共享参数定义和参数引用实现了k8s层的参数共享。

下面是部署配置文件里的环境变量的片段。“ - name: MYSQL_USER_PASSWORD”是环境变量名,“secretKeyRef”说明它的值来自于secret,“name: k8sdemo-secret”是secret的名字,“key: MYSQL_USER_PASSWORD”是secret里的键名,它的最终含义就是环境变量“MYSQL_USER_PASSWORD”的值是由secret里的量“MYSQL_USER_PASSWORD”来定义。

 env:     - name: MYSQL_USER_PASSWORD               valueFrom:                 secretKeyRef:                   name: k8sdemo-secret                   key: MYSQL_USER_PASSWORD

下面是另一个定义环境变量的片段,与上面的类似,只不过它的键值来自于configMap,而不是secret。

 env:      - name: MYSQL_DATABASE               valueFrom:                 configMapKeyRef:                   name: k8sdemo-config                   key: MYSQL_DATABASE

关于k8s的部署配置细节,请参阅“通过搭建MySQL掌握k8s(Kubernetes)重要概念(上):网络与持久卷”. "

程序和k8s的参数共享:

k8s在创建容器时,会创建环境变量。应用程序在容器里运行时可以从环境变量里读取共享参数已达到应用程序和k8s共享参数的目的。下面就是Go程序访问数据库的代码片段。