搜索
❏ 站外平台:

Kubernetes 分布式应用部署实战:以人脸识别应用为例

作者: Hannibal 译者: LCTT Andy Song

| 2018-07-30 18:24      

应用部署

Pods 和 服务概览:

MySQL

第一个要部署的服务是数据库。

按照 Kubernetes 的示例 Kubenetes MySQL 进行部署,即可以满足我的需求。注意:示例配置文件的 MYSQL_PASSWORD 字段使用了明文密码,我将使用 Kubernetes Secrets 对象以提高安全性。

我创建了一个 Secret 对象,对应的本地 yaml 文件如下:

apiVersion: v1
kind: Secret
metadata:
  name: kube-face-secret
type: Opaque
data:
  mysql_password: base64codehere
  mysql_userpassword: base64codehere

其中 base64 编码通过如下命令生成:

echo -n "ubersecurepassword" | base64
echo -n "root:ubersecurepassword" | base64

(LCTT 译注:secret yaml 文件中的 data 应该有两条,一条对应 mysql_password,仅包含密码;另一条对应 mysql_userpassword,包含用户和密码。后文会用到 mysql_userpassword,但没有提及相应的生成)

我的部署 yaml 对应部分如下:

...
- name: MYSQL_ROOT_PASSWORD
  valueFrom:
    secretKeyRef:
      name: kube-face-secret
      key: mysql_password
...

另外值得一提的是,我使用卷将数据库持久化,卷对应的定义如下:

...
        volumeMounts:
        - name: mysql-persistent-storage
          mountPath: /var/lib/mysql
...
      volumes:
      - name: mysql-persistent-storage
        persistentVolumeClaim:
          claimName: mysql-pv-claim
...

其中 presistentVolumeClain 是关键,告知 Kubernetes 当前资源需要持久化存储。持久化存储的提供方式对用户透明。类似 Pods,如果想了解更多细节,参考文档:Kubernetes 持久化存储

(LCTT 译注:使用 presistentVolumeClain 之前需要创建 presistentVolume,对于单节点可以使用本地存储,对于多节点需要使用共享存储,因为 Pod 可以能调度到任何一个节点)

使用如下命令部署 MySQL 服务:

kubectl apply -f mysql.yaml

这里比较一下 createapplyapply 是一种宣告式declarative的对象配置命令,而 create命令式imperative的命令。当下我们需要知道的是,create 通常对应一项任务,例如运行某个组件或创建一个部署;相比而言,当我们使用 apply 的时候,用户并没有指定具体操作,Kubernetes 会根据集群目前的状态定义需要执行的操作。故如果不存在名为 mysql 的服务,当我执行 apply -f mysql.yaml 时,Kubernetes 会创建该服务。如果再次执行这个命令,Kubernetes 会忽略该命令。但如果我再次运行 create,Kubernetes 会报错,告知服务已经创建。

想了解更多信息,请阅读如下文档:Kubernetes 对象管理命令式配置宣告式配置

运行如下命令查看执行进度信息:

# 描述完整信息
kubectl describe deployment mysql
# 仅描述 Pods 信息
kubectl get pods -l app=mysql

(第一个命令)输出示例如下:

...
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   mysql-55cd6b9f47 (1/1 replicas created)
...

对于 get pods 命令,输出示例如下:

NAME                     READY     STATUS    RESTARTS   AGE
mysql-78dbbd9c49-k6sdv   1/1       Running   0          18s

可以使用下面的命令测试数据库实例:

kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -pyourpasswordhere

特别提醒:如果你在这里修改了密码,重新 apply 你的 yaml 文件并不能更新容器。因为数据库是持久化的,密码并不会改变。你需要先使用 kubectl delete -f mysql.yaml 命令删除整个部署。

运行 show databases 后,应该可以看到如下信息:

If you don't see a command prompt, try pressing enter.

mysql>
mysql>
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| kube               |
| mysql              |
| performance_schema |
+--------------------+
4 rows in set (0.00 sec)

mysql> exit
Bye

你会注意到,我还将一个数据库初始化 SQL 文件挂载到容器中,MySQL 容器会自动运行该文件,导入我将用到的部分数据和模式。

对应的卷定义如下:

  volumeMounts:
  - name: mysql-persistent-storage
    mountPath: /var/lib/mysql
  - name: bootstrap-script
    mountPath: /docker-entrypoint-initdb.d/database_setup.sql
volumes:
- name: mysql-persistent-storage
  persistentVolumeClaim:
    claimName: mysql-pv-claim
- name: bootstrap-script
  hostPath:
    path: /Users/hannibal/golang/src/github.com/Skarlso/kube-cluster-sample/database_setup.sql
    type: File

(LCTT 译注:数据库初始化脚本需要改成对应的路径,如果是多节点,需要是共享存储中的路径。另外,作者给的 sql 文件似乎有误,person_images 表中的 person_id 列数字都小 1,作者默认 id 从 0 开始,但应该是从 1 开始)

运行如下命令查看引导脚本是否正确执行:

~/golang/src/github.com/Skarlso/kube-cluster-sample/kube_files master*
❯ kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -uroot -pyourpasswordhere kube
If you don't see a command prompt, try pressing enter.

mysql> show tables;
+----------------+
| Tables_in_kube |
+----------------+
| images         |
| person         |
| person_images  |
+----------------+
3 rows in set (0.00 sec)

mysql>

(LCTT 译注:上述代码块中的第一行是作者执行命令所在路径,执行第二行的命令无需在该目录中进行)

上述操作完成了数据库服务的初始化。使用如下命令可以查看服务日志:

kubectl logs deployment/mysql -f

NSQ 查询

NSQ 查询将以内部服务的形式运行。由于不需要外部访问,这里使用 clusterIP: None 在 Kubernetes 中将其设置为无头服务headless service,意味着该服务不使用负载均衡模式,也不使用单独的服务 IP。DNS 将基于服务选择器selectors

我们的 NSQ 查询服务对应的选择器为:

  selector:
    matchLabels:
      app: nsqlookup

那么,内部 DNS 对应的实体类似于:nsqlookup.default.svc.cluster.local

无头服务的更多细节,可以参考:无头服务

NSQ 服务与 MySQL 服务大同小异,只需要少许修改即可。如前所述,我将使用 NSQ 原生的 Docker 镜像,名称为 nsqio/nsq。镜像包含了全部的 nsq 命令,故 nsqd 也将使用该镜像,只是使用的命令不同。对于 nsqlookupd,命令如下:

command: ["/nsqlookupd"]
args: ["--broadcast-address=nsqlookup.default.svc.cluster.local"]

你可能会疑惑,--broadcast-address 参数是做什么用的?默认情况下,nsqlookup 使用容器的主机名作为广播地址;这意味着,当用户运行回调时,回调试图访问的地址类似于 http://nsqlookup-234kf-asdf:4161/lookup?topics=image,但这显然不是我们期望的。将广播地址设置为内部 DNS 后,回调地址将是 http://nsqlookup.default.svc.cluster.local:4161/lookup?topic=images,这正是我们期望的。

NSQ 查询还需要转发两个端口,一个用于广播,另一个用于 nsqd 守护进程的回调。在 Dockerfile 中暴露相应端口,在 Kubernetes 模板中使用它们,类似如下:

容器模板:

        ports:
        - containerPort: 4160
          hostPort: 4160
        - containerPort: 4161
          hostPort: 4161

服务模板:

spec:
  ports:
  - name: main
    protocol: TCP
    port: 4160
    targetPort: 4160
  - name: secondary
    protocol: TCP
    port: 4161
    targetPort: 4161

端口名称是必须的,Kubernetes 基于名称进行区分。(LCTT 译注:端口名更新为作者 GitHub 对应文件中的名称)

像之前那样,使用如下命令创建服务:

kubectl apply -f nsqlookup.yaml

nsqlookupd 部分到此结束。截至目前,我们已经准备好两个主要的组件。

接收器

这部分略微复杂。接收器需要完成三项工作:

  • 创建一些部署
  • 创建 nsq 守护进程
  • 将本服务对外公开

部署

第一个要创建的部署是接收器本身,容器镜像为 skarlso/kube-receiver-alpine

NSQ 守护进程

接收器需要使用 NSQ 守护进程。如前所述,接收器在其内部运行一个 NSQ,这样与 nsq 的通信可以在本地进行,无需通过网络。为了让接收器可以这样操作,NSQ 需要与接收器部署在同一个节点上。

NSQ 守护进程也需要一些调整的参数配置:

        ports:
        - containerPort: 4150
          hostPort: 4150
        - containerPort: 4151
          hostPort: 4151
        env:
        - name: NSQLOOKUP_ADDRESS
          value: nsqlookup.default.svc.cluster.local
        - name: NSQ_BROADCAST_ADDRESS
          value: nsqd.default.svc.cluster.local
        command: ["/nsqd"]
        args: ["--lookupd-tcp-address=$(NSQLOOKUP_ADDRESS):4160", "--broadcast-address=$(NSQ_BROADCAST_ADDRESS)"]

其中我们配置了 lookup-tcp-addressbroadcast-address 参数。前者是 nslookup 服务的 DNS 地址,后者用于回调,就像 nsqlookupd 配置中那样。

对外公开

下面即将创建第一个对外公开的服务。有两种方式可供选择。考虑到该 API 负载较高,可以使用负载均衡的方式。另外,如果希望将其部署到生产环境中的任选节点,也应该使用负载均衡方式。

但由于我使用的本地集群只有一个节点,那么使用 NodePort 的方式就足够了。NodePort 方式将服务暴露在对应节点的固定端口上。如果未指定端口,将从 30000-32767 数字范围内随机选其一个。也可以指定端口,可以在模板文件中使用 nodePort 设置即可。可以通过 <NodeIP>:<NodePort> 访问该服务。如果使用多个节点,负载均衡可以将多个 IP 合并为一个 IP。

更多信息,请参考文档:服务发布

结合上面的信息,我们定义了接收器服务,对应的模板如下:

apiVersion: v1
kind: Service
metadata:
  name: receiver-service
spec:
  ports:
  - protocol: TCP
    port: 8000
    targetPort: 8000
  selector:
    app: receiver
  type: NodePort

如果希望固定使用 8000 端口,需要增加 nodePort 配置,具体如下:

apiVersion: v1
kind: Service
metadata:
  name: receiver-service
spec:
  ports:
  - protocol: TCP
    port: 8000
    targetPort: 8000
  selector:
    app: receiver
  type: NodePort
  nodePort: 8000

(LCTT 译注:虽然作者没有写,但我们应该知道需要运行的部署命令 kubectl apply -f receiver.yaml。)

LCTT 译者
Andy Song 🌟🌟🌟🌟
共计翻译: 38.0 篇 | 共计贡献: 170
贡献时间:2018-03-26 -> 2018-09-12
访问我的 LCTT 主页 | 在 GitHub 上关注我


最新评论

我也要发表评论


返回顶部

分享到微信

打开微信,点击顶部的“╋”,
使用“扫一扫”将网页分享至微信。