这篇文章上次修改于 902 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

1 目标

在本地,一键启动 k8s 集群,并将服务部署到 k8s 集群上。

2 部署 k8s

2.1 部署 minikube

minikube 是一个虚拟机,启动后会在内部自动创建一个 k8s 集群。minikube 是 k8s 的 node。

2.1.1 安装 minikube

minikube 安装:

curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube

2.1.2 启动 minikube

minikube start \
        --extra-config=apiserver.service-node-port-range=1-65535 \
        --listen-address=0.0.0.0

其中,

  • --extra-config 用来修改 minikube 默认暴露给主机的端口范围,默认为:30000-32767。
  • --listen-address=0.0.0.0 是为了将 minikube 内部的服务暴露在 minikube 外部,或者也可以使用 ingress-nginx 插件。minikube 暴露给主机的地址为 192.168.49.2,假如 minikube 中服务监听的端口为 3000,则在主机上访问该服务的方式为 192.168.49.2:3000。可以用 minikube service --url 命令查看服务地址,例如:

    $ minikube service --url signaling-0
    http://192.168.49.2:21116

2.1.3 kubectl

kubectl 可方便地与 k8s 集群进行交互。minikube 内部默认安装 kubectl,例如查看所有 pod:

minikube kubectl -- get pods

可以为 minikube kubectl -- 命令起一个别名:

alias kubectl="minikube kubectl --"

查看所有 pod 命令可简化为:

kubectl get pods

kubectl 常用的命令:

  • 列出 pod,service 等资源:

    # List all pods in plain-text output format.
    kubectl get pods
    
    # List all services in plain-text output format.
    kubectl get svc
    
    # List all daemon sets in plain-text output format.
    kubectl get ds
  • 查看 pod 等资源的状态,事件等:

    # Display the details of all the pods that are managed by the replication controller named <rc-name>.
    # Remember: Any pods that are created by the replication controller get prefixed with the name of the replication controller.
    kubectl describe pods <rc-name>
  • 进入 pod 中的容器:

    # Get an interactive TTY and run /bin/bash from pod <pod-name>. By default, output is from the first container.
    kubectl exec -it <pod-name> -- /bin/bash

说明:pod 是 k8s 的管理单元,pod 中可包含多个容器,pod 中的所有容器共享 namespace。

2.1.4 其它命令

停止 minikube:

minikube stop

删除 k8s 集群:

minikube delete

2.2 部署 Lens

Lens 是 k8s 集群的操作界面,可方便地与 k8s 集群进行交互。

2.2.1 安装 Lens

wget https://api.k8slens.dev/binaries/Lens-5.4.4-latest.20220325.1.amd64.deb
sudo dpkg -i Lens-5.4.4-latest.20220325.1.amd64.deb

2.2.2 启动 Lens

  1. 命令方式:
lens
  1. 界面方式:在应用中搜索到 Lens 后双击。

2.2.3 Lens 管理 k8s

左边菜单栏中列出来 k8s 中的各种资源,可对各种资源进行查看、修改、删除。

例如,查看所有 pod:

img

对 Deployment 或 StatefulSet 扩缩容:

img

进入容器:

img

将 k8s 中 TCP 服务的端口映射到主机:

img

内置了 Helm,Helm 用来管理 k8s 应用,可发现、共享和使用为 k8s 构建的软件。

例如,在 k8s 安装 redis 或 nats:

img

3 部署 Nginx

部署:

docker run -d --name xremote_nginx --network host \
           -v ${HOME}/nginx/nginx.conf:/etc/nginx/nginx.conf nginx

${HOME}/nginx/nginx.conf 中添加了要代理的 minikube 中的 UDP 服务端口。示例如下:

stream {
  log_format proxy '$remote_addr [$time_local] '
                   '$protocol $status $bytes_sent $bytes_received '
                   '$session_time "$upstream_addr" '
                   '"$upstream_bytes_sent" "$upstream_bytes_received" "$upstream_connect_time"';

  access_log /var/log/nginx/udp-access.log proxy;
  open_log_file_cache off;

  upstream signaling-0 {
       server 192.168.49.2:21116;
  }
  server{
       listen 21116 udp reuseport;
       proxy_connect_timeout 8s;
       proxy_pass signaling-0;
  }
}

使用 Nginx 的原因:代理 minikube 中的 UDP 服务。

  • 使用 NodePort 等 service 可将 k8s 中的服务暴露给 node,也就是 minikube,而不是主机。
  • minikube 暴露给主机的地址默认为 192.168.49.2,假如 minikube 中服务监听的端口为 3000,则在主机上访问该服务的方式为 192.168.49.2:3000,而不是 ${host_ip}:3000。所以,如果需要从另外一台主机访问 minikube 中的服务,需要 nginx 进行代理。
  • 对于 minikube 中的 TCP 服务,可不通过 nginx 进行代理,可使用 kubectl port-forward 命令直接将端口映射到主机。

4 部署服务

4.1 部署文件

deployment.yml:定义资源如何部署。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: signaling-0
spec:
  selector:
    matchLabels:
      app: signaling-0
  replicas: 1
  template:
    metadata:
      labels:
        app: signaling-0
    spec:
      containers:
        - name: signaling
          image: code.com:6543/signaling-server:latest
          ports:
            - name: server
              containerPort: 21116
              protocol: UDP
          imagePullPolicy: IfNotPresent
          env:
            - name: RUN_XREMOTE_SERVER_MODE
              value: production
          command: ["/bin/bash"]
          args: ["-c", "/opt/signaling_server/run.sh"]
          volumeMounts:
            - name: config
              mountPath: /opt/signaling_server/config/signaling/production.toml
              subPath: production.toml
      volumes:
        - name: config
          configMap:
            name: signaling

1.

字段说明:

  • kind:指定了这个 API 对象的类型是一个 Deployment。

    • Deployment:是一个定义多副本应用(即多个副本 Pod)的对象。此外,Deployment 还负责在 Pod 定义发生变化时,对每个副本进行滚动更新(Rolling Update)。

      • 场景:一个应用的所有 Pod,是完全一样的。所以,它们互相之间没有顺序,也无所谓运行在哪台宿主机上。
      • Pod :如果 k8s 看作操作系统,pod 可看作进程组,容器可看作进程。

        • 凡是调度、网络、存储,以及安全相关的属性,基本上是 Pod 级别的。
        • Pod 里的所有容器,共享的是同一个 Network Namespace,并且可以声明共享同一个 Volume。
      • 另一种常见的 kind 是 StatefulSet,相对于 Deployment,一个应用的 Pod 之间存在着依赖关系,比如:主从关系、主备关系。redis 和 nats 是 StatefulSet。
  • metadata:是 API 对象的“标识”,即元数据,它也是我们从 Kubernetes 里找到这个对象的主要依据。
  • spec:用来描述它所要表达的功能。

    • spec.selector.matchLabels:过滤规则,一般称之为:Label Selector。这个字段一般和 spec.template.metadata.labels.app 保持一致,表示该 deployment 控制下面 spec 中定义的 Pod。

      • 使用一种 API 对象(Deployment)管理另一种 API 对象(Pod)的方法,在 Kubernetes 中,叫作“控制器”模式(controller pattern)
    • spec.replicas:Pod 副本个数。
    • spec.template:Pod 模板,描述了我想要创建的 Pod 的细节。本例中,Pod 里只有一个容器,该容器的镜像(spec.containers.image)是 code.autox.ds:6543/internal/mapping-experimental/offboard/signaling-server:latest,监听端口(containerPort)是 21116,将 signaling 这个配置挂载到了 /opt/signaling_server/config/signaling/production.toml,后面会提到 ConfigMap。

config_map.yml:存储一些配置信息。

apiVersion: v1
kind: ConfigMap
metadata:
  name: signaling
data:
  production.toml: |-
    debug = false

    [signaling]
    data_center = "beijing"
    service_name = "signaling"
    signaling_port = 21116

在 Kubernetes 中,有几种特殊的 Volume,它们的作用是为容器提供预先定义好的数据。所以,从容器的角度来看,这些 Volume 里的信息就是仿佛是被 K8s “投射”(Project)进入容器当中的。到目前为止,Kubernetes 支持的 Projected Volume 一共有四种:Secret、ConfigMap、Downward API 和 ServiceAccountToken。

前面的 deployment.yml 中的 spec.template.spec.volumes 中会声明一个 volume,该 volume 的来源是一个 ConfigMap,然后该 volume 会被挂载到 /opt/signaling_server/config/signaling/production.toml。

service.yml:service 的种类较多,其中 NodePort 的作用是将服务端口暴露在 k8s 外部。

kind: Service
apiVersion: v1
metadata:
  name: signaling-0
spec:
  ports:
    - name: server
      nodePort: 21116
      port: 21116
      targetPort: 21116
      protocol: UDP
  selector:
    app: signaling-0
  type: NodePort

将服务暴露在集群外的方式:

  • Service

    • NodePort:最简单。
    • LoadBalancer:适用于公有云。
    • ExternalName:Kubernetes 在 1.7 之后支持的一个新特性。
  • Ingress:是 Service 的“Service”。

本例中:

  • type=NodePort
  • ports 字段

    • nodePort:暴露在集群外部的端口。
    • port:暴露在集群内部的端口。
    • targetPort:容器监听的端口。
  • selector 字段表示它代理的是 signaling-0 这个 Pod 的端口。

要访问这个 Service,只需要访问:

<node 的 IP 地址>:21116
  • 另一种 service:ClusterIP

    • 将服务暴露在集群内部,即在集群中的一个 pod 内部可访问到另一个 pod。nats 和 redis 有用到。
    • 当 ClusterIP 中将 clusterIP 字段设置为 None,便不会被分配 VIP,被称为 Headless Service,它所代理的所有 Pod 的 IP 地址,都会被绑定一个 DNS 记录,形式为 .。例如,redis 的 headless 定义如下:

      apiVersion: v1
      kind: Service
      metadata:
        name: redis-headless
        namespace: default
      spec:
        ...
        clusterIP: None
        type: ClusterIP
        ...

      通过域名 redis-headless.default 即可访问 redis。

以上几个 yml 文件也可以放在一个 yml 文件中,并以 ”---“ 分隔。

4.2 部署

对每个 yml 文件执行:

kubectl apply -f xxx.yml

5 查看日志

方式 1)Lens 界面方式:

img

方式 2)命令行方式:

$ kubectl get pods
NAME                          READY   STATUS    RESTARTS      AGE
discovery-568569c86f-jflrq    1/1     Running   1 (51m ago)   58m

$ kubectl exec -it discovery-568569c86f-jflrq -- /bin/sh
# ls  
config    discovery-server  log  run.sh  sum.md5
# ls log   
discovery_server_20220408-0607.log

说明:由于 服务的日志写在了文件中,而不是标准输出中,所以用 kubectl logs 命令看不到日志。

6 错误处理

6.1 拉取镜像失败

发生 ErrImagePull 错误:

$ kubectl get pods
NAME                          READY   STATUS             RESTARTS   AGE
discovery-568569c86f-njmf4    0/1     ErrImagePull       0          41m

是因为 minikube 没有权限拉取镜像。

解决:

minikube -p minikube docker-env
eval $(minikube -p minikube docker-env)
cd xremote/server
bash release_build.sh discovery

相当于把镜像 push 到了 minikube 中。

或者这种方式应该也可以:https://minikube.sigs.k8s.io/docs/handbook/untrusted_certs/ ,本人没有尝试。