13、本地使用 Kubernetes 部署应用

本地使用 Kubernetes 部署应用

1. 引言

构建服务后,部署是关键的一步。本文将介绍如何使用 Kubernetes 和 Helm 在本地部署服务集群,具体步骤如下:
- 创建代理命令行界面(CLI)作为服务的可执行文件。
- 配置 Kubernetes 和 Helm,以便在本地机器和云平台上编排服务。
- 在本地机器上运行服务集群。

2. Kubernetes 简介

Kubernetes 是一个开源的容器编排系统,用于自动化部署、扩展和管理容器化服务。它通过 REST API 创建、更新和删除资源,是一个声明式系统,用户只需描述最终状态,Kubernetes 会自动将系统从当前状态转换到目标状态。

Kubernetes 中最常见的资源是 Pod,它是最小的可部署单元。Pod 内的容器共享网络命名空间、IP 地址和进程间通信(IPC)命名空间,还可以共享卷。其他资源包括 ConfigMaps、Secrets 用于配置 Pod,Deployments、StatefulSets、DaemonSets 用于管理 Pod 集合。用户还可以通过创建自定义资源和控制器来扩展 Kubernetes。

与 Kubernetes 交互需要使用命令行工具 kubectl,下面将介绍其安装方法。

3. 安装 kubectl

kubectl 用于对 Kubernetes 集群执行命令,可用于检查和管理服务的集群资源以及查看日志。对于一次性操作,建议使用 kubectl;对于重复操作,如部署或升级服务,可使用 Helm 包管理器或操作符。

安装 kubectl 的步骤如下:

$ curl -LO \
https://storage.googleapis.com/kubernetes-release/release/\
v1.18.0/bin/$(uname)/amd64/kubectl
$ chmod +x ./kubectl
$ mv ./kubectl /usr/local/bin/kubectl

安装完成后,需要一个 Kubernetes 集群和其 API 才能使用 kubectl。接下来将使用 Kind 工具在 Docker 中运行本地 Kubernetes 集群。

4. 使用 Kind 进行本地开发和持续集成

Kind(Kubernetes IN Docker)是 Kubernetes 团队开发的工具,用于使用 Docker 容器作为节点运行本地 Kubernetes 集群。它是运行自己的 Kubernetes 集群的最简单方法,适用于本地开发、测试和持续集成。

4.1 安装 Kind

安装 Kind 的步骤如下:

$ curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.8.1/kind-$(uname)-amd64
$ chmod +x ./kind
$ mv ./kind /usr/local/bin/kind

使用 Kind 前,需要安装 Docker,请参考 Docker 的官方安装说明进行安装。

4.2 创建 Kind 集群

启动 Docker 后,创建 Kind 集群的命令如下:

$ kind create cluster

验证 Kind 是否创建了集群并配置 kubectl 使用该集群:

$ kubectl cluster-info
> Kubernetes master is running at https://127.0.0.1:46023
KubeDNS is running at \
https://127.0.0.1:46023/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

可以运行以下命令查看 Node 容器:

$ docker ps
CONTAINER ID IMAGE COMMAND CREATED ...
033de99b1e53 kindest/node:v1.18.2 "/usr/local/bin/entr…" 2 minutes...

现在已经有了一个运行的 Kubernetes 集群,接下来需要一个 Docker 镜像和可执行的入口点来运行服务。下面将编写一个代理 CLI 作为服务的可执行文件。

5. 编写代理命令行界面

代理 CLI 将提供足够的功能,作为 Docker 镜像的入口点,用于解析标志、配置和运行代理。这里使用 Cobra 库处理命令和标志,它适用于创建简单的 CLI 和复杂的应用程序,并且与 Viper 库集成,Viper 是 Go 应用程序的完整配置解决方案。

5.1 创建主文件

创建 cmd/proglog/main.go 文件,代码如下:

package main

import (
    "log"
    "os"
    "os/signal"
    "path"
    "syscall"
    "github.com/spf13/cobra"
    "github.com/spf13/viper"
    "github.com/travisjeffery/proglog/internal/agent"
    "github.com/travisjeffery/proglog/internal/config"
)

func main() {
    cli := &cli{}
    cmd := &cobra.Command{
        Use:    "proglog",
        PreRunE: cli.setupConfig,
        RunE:   cli.run,
    }
    if err := setupFlags(cmd); err != nil {
        log.Fatal(err)
    }
    if err := cmd.Execute(); err != nil {
        log.Fatal(err)
    }
}

5.2 定义类型

定义 cli cfg 类型,代码如下:

type cli struct {
    cfg cfg
}

type cfg struct {
    agent.Config
    ServerTLSConfig config.TLSConfig
    PeerTLSConfig   config.TLSConfig
}

5.3 暴露标志

main.go 文件中添加以下代码来声明 CLI 的标志:

func setupFlags(cmd *cobra.Command) error {
    hostname, err := os.Hostname()
    if err != nil {
        log.Fatal(err)
    }
    cmd.Flags().String("config-file", "", "Path to config file.")
    dataDir := path.Join(os.TempDir(), "proglog")
    cmd.Flags().String("data-dir", dataDir, "Directory to store log and Raft data.")
    cmd.Flags().String("node-name", hostname, "Unique server ID.")
    cmd.Flags().String("bind-addr", "127.0.0.1:8401", "Address to bind Serf on.")
    cmd.Flags().Int("rpc-port", 8400, "Port for RPC clients (and Raft) connections.")
    cmd.Flags().StringSlice("start-join-addrs", nil, "Serf addresses to join.")
    cmd.Flags().Bool("bootstrap", false, "Bootstrap the cluster.")
    cmd.Flags().String("acl-model-file", "", "Path to ACL model.")
    cmd.Flags().String("acl-policy-file", "", "Path to ACL policy.")
    cmd.Flags().String("server-tls-cert-file", "", "Path to server tls cert.")
    cmd.Flags().String("server-tls-key-file", "", "Path to server tls key.")
    cmd.Flags().String("server-tls-ca-file", "", "Path to server certificate authority.")
    cmd.Flags().String("peer-tls-cert-file", "", "Path to peer tls cert.")
    cmd.Flags().String("peer-tls-key-file", "", "Path to peer tls key.")
    cmd.Flags().String("peer-tls-ca-file", "", "Path to peer certificate authority.")
    return viper.BindPFlags(cmd.Flags())
}

这些标志允许用户配置代理并了解默认配置。

5.4 管理配置

Viper 提供了一个集中的配置注册表系统,支持通过标志、文件或从服务(如 Consul)加载动态配置。配置文件可以支持对运行中的服务进行动态配置更改,服务会监视配置文件的更改并相应更新。

main.go 文件中添加以下代码来设置配置:

func (c *cli) setupConfig(cmd *cobra.Command, args []string) error {
    var err error
    configFile, err := cmd.Flags().GetString("config-file")
    if err != nil {
        return err
    }
    viper.SetConfigFile(configFile)
    if err = viper.ReadInConfig(); err != nil {
        // it's ok if config file doesn't exist
        if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
            return err
        }
    }
    c.cfg.DataDir = viper.GetString("data-dir")
    c.cfg.NodeName = viper.GetString("node-name")
    c.cfg.BindAddr = viper.GetString("bind-addr")
    c.cfg.RPCPort = viper.GetInt("rpc-port")
    c.cfg.StartJoinAddrs = viper.GetStringSlice("start-join-addrs")
    c.cfg.Bootstrap = viper.GetBool("bootstrap")
    c.cfg.ACLModelFile = viper.GetString("acl-mode-file")
    c.cfg.ACLPolicyFile = viper.GetString("acl-policy-file")
    c.cfg.ServerTLSConfig.CertFile = viper.GetString("server-tls-cert-file")
    c.cfg.ServerTLSConfig.KeyFile = viper.GetString("server-tls-key-file")
    c.cfg.ServerTLSConfig.CAFile = viper.GetString("server-tls-ca-file")
    c.cfg.PeerTLSConfig.CertFile = viper.GetString("peer-tls-cert-file")
    c.cfg.PeerTLSConfig.KeyFile = viper.GetString("peer-tls-key-file")
    c.cfg.PeerTLSConfig.CAFile = viper.GetString("peer-tls-ca-file")
    if c.cfg.ServerTLSConfig.CertFile != "" &&
        c.cfg.ServerTLSConfig.KeyFile != "" {
        c.cfg.ServerTLSConfig.Server = true
        c.cfg.Config.ServerTLSConfig, err = config.SetupTLSConfig(
            c.cfg.ServerTLSConfig,
        )
        if err != nil {
            return err
        }
    }
    if c.cfg.PeerTLSConfig.CertFile != "" &&
        c.cfg.PeerTLSConfig.KeyFile != "" {
        c.cfg.Config.PeerTLSConfig, err = config.SetupTLSConfig(
            c.cfg.PeerTLSConfig,
        )
        if err != nil {
            return err
        }
    }
    return nil
}

5.5 完成程序

main.go 文件中添加以下 run 方法:

func (c *cli) run(cmd *cobra.Command, args []string) error {
    var err error
    agent, err := agent.New(c.cfg.Config)
    if err != nil {
        return err
    }
    sigc := make(chan os.Signal, 1)
    signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)
    <-sigc
    return agent.Shutdown()
}

run 方法的功能如下:
- 创建代理。
- 处理来自操作系统的信号。
- 当操作系统终止程序时,优雅地关闭代理。

现在已经有了可作为 Docker 镜像入口点的可执行文件,接下来将编写 Dockerfile 并构建镜像。

6. 构建 Docker 镜像

创建 Dockerfile,代码如下:

FROM golang:1.14-alpine AS build
WORKDIR /go/src/proglog
COPY . .
RUN CGO_ENABLED=0 go build -o /go/bin/proglog ./cmd/proglog
FROM scratch
COPY --from=build /go/bin/proglog /bin/proglog
ENTRYPOINT ["/bin/proglog"]

该 Dockerfile 使用多阶段构建,一个阶段用于构建服务,另一个阶段用于运行服务。这样可以使 Dockerfile 易于阅读和维护,同时保持构建效率和镜像的小体积。

Makefile 文件底部添加以下代码,以添加构建 Docker 镜像的目标:

TAG ?= 0.0.1
build-docker:
    docker build -t github.com/travisjeffery/proglog:$(TAG) .

构建镜像并将其加载到 Kind 集群中:

$ make build-docker
$ kind load docker-image github.com/travisjeffery/proglog:0.0.1

现在已经有了 Docker 镜像,接下来将介绍如何使用 Helm 配置和部署服务集群。

7. 使用 Helm 配置和部署服务

Helm 是 Kubernetes 的包管理器,用于在 Kubernetes 中分发和安装服务。Helm 包称为图表(charts),它定义了在 Kubernetes 集群中运行服务所需的所有资源,如部署、服务、持久卷声明等。

7.1 安装 Helm

安装 Helm 的命令如下:

$ curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 \
| bash

7.2 安装现有图表

在编写自己的 Helm 图表之前,先安装一个现有的图表。以 Bitnami 维护的 Nginx 图表为例,操作步骤如下:
1. 添加 Bitnami 仓库:

$ helm repo add bitnami https://charts.bitnami.com/bitnami
  1. 安装 Nginx 图表:
$ helm install my-nginx bitnami/nginx
  1. 查看发布信息:
$ helm list
NAME      NAMESPACE  REVISION  UPDATED       STATUS...
my-nginx  default    1         2020...       deployed...
  1. 验证 Nginx 是否运行:
$ POD_NAME=$(kubectl get pod \
--selector=app.kubernetes.io/name=nginx \
--template '{{index .items 0 "metadata" "name" }}')
$ SERVICE_IP=$(kubectl get svc \
--namespace default my-nginx --template "{{ .spec.clusterIP }}")
$ kubectl exec $POD_NAME curl $SERVICE_IP

如果需要卸载 Nginx 发布,可运行以下命令:

$ helm uninstall my-nginx
release "my-nginx" uninstalled

7.3 构建自己的 Helm 图表

接下来将为服务构建一个 Helm 图表,并在 Kind 集群中安装该集群。

7.3.1 创建 Helm 图表

创建 Helm 图表的命令如下:

$ mkdir deploy && cd deploy
$ helm create proglog

proglog 目录包含以下文件和目录:

.
└──proglog
    ├──charts
    ├──Chart.yaml
    ├──templates
    │   ├──deployment.yaml
    │   ├──_helpers.tpl
    │   ├──ingress.yaml
    │   ├──NOTES.txt
    │   ├──serviceaccount.yaml
    │   ├──service.yaml
    │   └──tests
    │       └──test-connection.yaml
    └──values.yaml
4 directories, 9 files
  • Chart.yaml :描述图表,可在模板中访问其中的数据。
  • charts 目录:可能包含子图表。
  • values.yaml :包含图表的默认值,用户在安装或升级图表时可以覆盖这些值。
  • templates 目录:包含模板文件,使用 Go 模板语言编写,用于生成有效的 Kubernetes 清单文件。

可以使用以下命令在本地渲染模板,而不应用资源:

$ helm template proglog

由于不需要示例模板,可运行以下命令删除它们:

$ rm proglog/templates/**/*.yaml proglog/templates/NOTES.txt

我们的服务需要两种资源类型:StatefulSet 和 Service,接下来将分别创建相应的文件。

7.3.2 StatefulSets in Kubernetes

StatefulSets 用于管理 Kubernetes 中的有状态应用程序,如我们的日志持久化服务。有状态服务需要满足以下条件时使用 StatefulSet:
- 稳定、唯一的网络标识符:服务中的每个节点需要唯一的节点名称作为标识符。
- 稳定、持久的存储:服务写入的数据需要在重启后持久化。
- 有序、优雅的部署和扩展:服务需要初始节点引导集群,并将后续节点加入集群。
- 有序、自动化的滚动更新:集群始终需要有一个领导者,滚动领导者时需要给集群足够的时间选举新的领导者。

如果服务是无状态的,不需要这些功能,则应使用 Deployment 代替 StatefulSet。例如,将数据持久化到关系型数据库(如 Postgres)的 API 服务,API 服务可以使用 Deployment 运行,而 Postgres 可以使用 StatefulSet 运行。

创建 deploy/proglog/templates/statefulset.yaml 文件,代码如下:

apiVersion: apps/v1
kind: StatefulSet
metadata:
    name: {{ include "proglog.fullname" . }}
    namespace: {{ .Release.Namespace }}
    labels: {{ include "proglog.labels" . | nindent 4 }}
spec:
    selector:
        matchLabels: {{ include "proglog.selectorLabels" . | nindent 6 }}
    serviceName: {{ include "proglog.fullname" . }}
    replicas: {{ .Values.replicas }}
    template:
        metadata:
            name: {{ include "proglog.fullname" . }}
            labels: {{ include "proglog.labels" . | nindent 8 }}
        spec:
            # initContainers...
            # containers...
            volumeClaimTemplates:
            - metadata:
                name: datadir
              spec:
                accessModes: [ "ReadWriteOnce" ]
                resources:
                    requests:
                        storage: {{ .Values.storage }}

上述代码中省略了 initContainers containers 字段,下面将填充这些内容。

7.3.3 填充 initContainers

initContainers... 替换为以下代码:

initContainers:
- name: {{ include "proglog.fullname" . }}-config-init
    image: busybox
    imagePullPolicy: IfNotPresent
    command:
    - /bin/sh
    - -c
    - |-
        ID=$(echo $HOSTNAME | rev | cut -d- -f1 | rev)
        cat > /var/run/proglog/config.yaml <<EOD
        data-dir: /var/run/proglog/data
        rpc-port: {{.Values.rpcPort}}
        # Make sure the following three key-values are on one line each in
        # your code. I split them across multiple lines to fit them in
        # for the book.
        bind-addr: \
        "$HOSTNAME.proglog.{{.Release.Namespace}}.\svc.cluster.local:\
        {{.Values.serfPort}}"
        bootstrap: $([ $ID = 0 ] && echo true || echo false)
        $([ $ID != 0 ] && echo 'start-join-addrs: \
        "proglog-0.proglog.{{.Release.Namespace}}.svc.cluster.local:\
        {{.Values.serfPort}}"')
        EOD
    volumeMounts:
    - name: datadir
        mountPath: /var/run/proglog

初始化容器在 StatefulSet 的应用容器之前运行,用于设置服务的配置文件。第一个服务器配置为引导 Raft 集群,后续服务器配置为加入集群。将 datadir 卷挂载到容器中,以便写入应用容器稍后将读取的配置文件。

7.3.4 填充 containers

containers... 替换为以下代码:

containers:
- name: {{ include "proglog.fullname" . }}
    image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
    ports:
    - containerPort: {{ .Values.rpcPort }}
        name: rpc
    - containerPort: {{ .Values.serfPort }}
        name: serf
    args:
    - --config-file=/var/run/proglog/config.yaml
    # probes...
    volumeMounts:
    - name: datadir
        mountPath: /var/run/proglog

这些容器定义了 StatefulSet 的应用容器,将卷挂载到容器中,用于读取配置文件和持久化日志。使用标志告诉服务配置文件的位置。

7.3.5 容器探针和 gRPC 健康检查

Kubernetes 使用探针来确定是否需要对容器采取行动,以提高服务的可靠性。探针分为以下三种类型:
| 探针类型 | 功能 | 调用时机 |
| — | — | — |
| 存活探针(Liveness probes) | 指示容器是否存活,否则 Kubernetes 将重启容器 | 容器的整个生命周期 |
| 就绪探针(Readiness probes) | 检查容器是否准备好接受流量,否则 Kubernetes 将从服务负载均衡器中移除该 Pod | 容器的整个生命周期 |
| 启动探针(Startup probes) | 指示容器应用程序是否已启动,Kubernetes 可以开始进行存活和就绪探测 | 服务初始化前,启动后不再调用 |

运行探针的方式有三种:
- 对服务器进行 HTTP 请求。
- 对服务器打开 TCP 套接字。
- 在容器中运行命令(例如,Postgres 有一个名为 pg_isready 的命令用于连接到 Postgres 服务器)。

gRPC 服务通常使用 grpc_health_probe 命令,要求服务器满足 gRPC 健康检查协议。我们的服务器需要导出以下服务:

syntax = "proto3";
package grpc.health.v1;

message HealthCheckRequest {
    string service = 1;
}

message HealthCheckResponse {
    enum ServingStatus {
        UNKNOWN = 0;
        SERVING = 1;
        NOT_SERVING = 2;
    }
    ServingStatus status = 1;
}

service Health {
    rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
    rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}

接下来将更新服务器以导出健康检查服务。

7.3.6 更新服务器以支持健康检查

打开 internal/server/server.go 文件,添加以下导入:

import (
    "context"
    "time"
    api "github.com/travisjeffery/proglog/api/v1"
    grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
    grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
    grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
    grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags"
    "go.opencensus.io/plugin/ocgrpc"
    "go.opencensus.io/stats/view"
    "go.opencensus.io/trace"
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
    "google.golang.org/grpc"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc/peer"
    "google.golang.org/grpc/status"
    "google.golang.org/grpc/health"
    healthpb "google.golang.org/grpc/health/grpc_health_v1"
)

更新 NewGRPCServer() 函数,添加以下高亮行:

func NewGRPCServer(config *Config, grpcOpts ...grpc.ServerOption) (
    *grpc.Server,
    error,
) {
    logger := zap.L().Named("server")
    zapOpts := []grpc_zap.Option{
        grpc_zap.WithDurationField(
            func(duration time.Duration) zapcore.Field {
                return zap.Int64(
                    "grpc.time_ns",
                    duration.Nanoseconds(),
                )
            },
        ),
    }
    trace.ApplyConfig(trace.Config{
        DefaultSampler: trace.AlwaysSample(),
    })
    err := view.Register(ocgrpc.DefaultServerViews...)
    if err != nil {
        return nil, err
    }
    grpcOpts = append(grpcOpts,
        grpc.StreamInterceptor(
            grpc_middleware.ChainStreamServer(
                grpc_ctxtags.StreamServerInterceptor(),
                grpc_zap.StreamServerInterceptor(
                    logger, zapOpts...,
                ),
                grpc_auth.StreamServerInterceptor(
                    authenticate,
                ),
            )), grpc.UnaryInterceptor(
            grpc_middleware.ChainUnaryServer(
                grpc_ctxtags.UnaryServerInterceptor(),
                grpc_zap.UnaryServerInterceptor(
                    logger, zapOpts...,
                ),
                grpc_auth.UnaryServerInterceptor(
                    authenticate,
                ),
            )),
        grpc.StatsHandler(&ocgrpc.ServerHandler{}),
    )
    gsrv := grpc.NewServer(grpcOpts...)
    hsrv := health.NewServer()
    hsrv.SetServingStatus("", healthpb.HealthCheckResponse_SERVING)
    healthpb.RegisterHealthServer(gsrv, hsrv)
    srv, err := newgrpcServer(config)
    if err != nil {
        return nil, err
    }
    api.RegisterLogServer(gsrv, srv)
    return gsrv, nil
}

这些代码创建了一个支持健康检查协议的服务,设置其服务状态为服务中,以便探针知道服务存活并准备好接受连接。然后将该服务注册到服务器,以便 gRPC 可以调用其端点。

7.3.7 配置探针

deploy/proglog/templates/statefulset.yaml 中的 probes... 替换为以下代码:

readinessProbe:
    exec:
        command: ["/bin/grpc_health_probe", "-addr=:{{ .Values.rpcPort }}"]
    initialDelaySeconds: 10
livenessProbe:
    exec:
        command: ["/bin/grpc_health_probe", "-addr=:{{ .Values.rpcPort }}"]
    initialDelaySeconds: 10

在 Dockerfile 中添加以下代码,安装 grpc_health_probe 可执行文件:

FROM golang:1.14-alpine AS build
WORKDIR /go/src/proglog
COPY . .
RUN CGO_ENABLED=0 go build -o /go/bin/proglog ./cmd/proglog
RUN GRPC_HEALTH_PROBE_VERSION=v0.3.2 && \
    wget -qO/go/bin/grpc_health_probe \
    https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/\
    ${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \
    chmod +x /go/bin/grpc_health_probe
FROM scratch
COPY --from=build /go/bin/proglog /bin/proglog
COPY --from=build /go/bin/grpc_health_probe /bin/grpc_health_probe
ENTRYPOINT ["/bin/proglog"]
7.3.8 Kubernetes 服务

Kubernetes 中的服务将应用程序作为网络服务暴露,有以下四种类型:
| 服务类型 | 功能 |
| — | — |
| ClusterIP | 在负载均衡的集群内部 IP 上暴露服务,仅在 Kubernetes 集群内可访问 |
| NodePort | 在每个节点的 IP 上的静态端口上暴露服务,可在 Kubernetes 集群外部访问 |
| LoadBalancer | 使用云提供商的负载均衡器在外部暴露服务,自动创建 ClusterIP 和 NodeIP 服务并管理路由 |
| ExternalName | 作为 DNS 名称的别名 |

不建议使用 NodePort 服务(除了 LoadBalancer 服务为您创建的服务),建议使用 LoadBalancer 或 ClusterIP 服务。

创建 deploy/proglog/templates/service.yaml 文件,用于服务模板:

# 此处代码原文未给出完整内容,可根据实际需求补充

综上所述,本文详细介绍了在本地使用 Kubernetes 和 Helm 部署服务集群的步骤,包括创建代理 CLI、构建 Docker 镜像、使用 Helm 配置和部署服务等。通过这些步骤,您可以在本地环境中轻松部署和管理服务。

7.3.8 Kubernetes 服务(续)

Kubernetes 中的服务将应用程序作为网络服务暴露,有以下四种类型:
| 服务类型 | 功能 |
| — | — |
| ClusterIP | 在负载均衡的集群内部 IP 上暴露服务,仅在 Kubernetes 集群内可访问 |
| NodePort | 在每个节点的 IP 上的静态端口上暴露服务,可在 Kubernetes 集群外部访问 |
| LoadBalancer | 使用云提供商的负载均衡器在外部暴露服务,自动创建 ClusterIP 和 NodeIP 服务并管理路由 |
| ExternalName | 作为 DNS 名称的别名 |

不建议使用 NodePort 服务(除了 LoadBalancer 服务为您创建的服务),建议使用 LoadBalancer 或 ClusterIP 服务。

创建 deploy/proglog/templates/service.yaml 文件,以下是一个示例代码:

apiVersion: v1
kind: Service
metadata:
  name: {{ include "proglog.fullname" . }}
  namespace: {{ .Release.Namespace }}
  labels:
    {{- include "proglog.labels" . | nindent 4 }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: rpc
      protocol: TCP
      name: rpc
  selector:
    {{- include "proglog.selectorLabels" . | nindent 4 }}

这个 service.yaml 文件定义了一个 Kubernetes 服务,它将根据 values.yaml 中的配置暴露相应的端口,并选择匹配标签的 Pod。

7.3.9 总结 Helm 图表配置

到这里,我们已经完成了 Helm 图表的主要配置,下面是整个配置流程的 mermaid 流程图:

graph LR
    A[创建 Helm 图表] --> B[定义 StatefulSet]
    B --> C[配置 initContainers]
    C --> D[配置 containers]
    D --> E[配置容器探针]
    E --> F[定义服务]

8. 部署服务到 Kubernetes 集群

现在我们已经完成了所有的准备工作,可以将服务部署到 Kubernetes 集群中了。

8.1 安装 Helm 图表

使用以下命令安装我们创建的 Helm 图表:

$ helm install proglog-service ./deploy/proglog

这个命令会将 deploy/proglog 目录下的 Helm 图表安装到 Kubernetes 集群中,并创建一个名为 proglog-service 的发布。

8.2 验证部署

安装完成后,可以使用以下命令验证服务是否成功部署:

$ kubectl get pods

如果一切正常,你应该看到与 proglog 相关的 Pod 处于运行状态。

还可以使用以下命令查看服务的详细信息:

$ kubectl describe service proglog-service
8.3 测试服务

可以使用 grpcurl 等工具测试服务是否正常工作。例如:

$ grpcurl -plaintext <service-ip>:<rpc-port> grpc.health.v1.Health/Check

其中 <service-ip> 是服务的 IP 地址,可以通过 kubectl get service 命令获取, <rpc-port> 是服务的 RPC 端口。

9. 服务的扩展和管理

9.1 扩展服务

如果需要扩展服务的副本数量,可以使用以下命令:

$ helm upgrade proglog-service ./deploy/proglog --set replicas=3

这个命令会将 proglog-service 的副本数量扩展到 3。

9.2 滚动更新

当需要更新服务的版本时,可以通过更新 Docker 镜像标签并使用 helm upgrade 命令进行滚动更新:

$ make build-docker TAG=0.0.2
$ kind load docker-image github.com/travisjeffery/proglog:0.0.2
$ helm upgrade proglog-service ./deploy/proglog --set image.tag=0.0.2
9.3 卸载服务

如果需要卸载服务,可以使用以下命令:

$ helm uninstall proglog-service

10. 总结

本文详细介绍了在本地使用 Kubernetes 和 Helm 部署服务集群的完整流程,包括:
1. 了解 Kubernetes 的基本概念和 kubectl 工具的安装。
2. 使用 Kind 工具创建本地 Kubernetes 集群。
3. 编写代理命令行界面(CLI)作为服务的可执行文件。
4. 构建 Docker 镜像并加载到 Kind 集群中。
5. 安装 Helm 并使用它来配置和部署服务。
6. 创建和配置 Helm 图表,包括 StatefulSet、服务和容器探针。
7. 将服务部署到 Kubernetes 集群中,并进行验证和测试。
8. 介绍了服务的扩展、滚动更新和卸载等管理操作。

通过这些步骤,你可以在本地环境中轻松部署、管理和扩展服务,为后续的开发和测试工作提供了坚实的基础。同时,掌握这些技术也有助于你在生产环境中更好地使用 Kubernetes 和 Helm 来管理复杂的应用程序。

基于51单片机,实现对直流电机的调速、测速以及正反转控制。项目包含完整的仿真文件、源程序、原理图和PCB设计文件,适合学习和实践51单片机在电机控制方面的应用。 功能特点 调速控制:通过按键调整PWM占空比,实现电机的速度调节。 测速功能:采用霍尔传感器非接触式测速,实时显示电机转速。 正反转控制:通过按键切换电机的正转和反转状态。 LCD显示:使用LCD1602液晶显示屏,显示当前的转速和PWM占空比。 硬件组成 主控制器:STC89C51/52单片机(与AT89S51/52、AT89C51/52通用)。 测速传感器:霍尔传感器,用于非接触式测速。 显示模块:LCD1602液晶显示屏,显示转速和占空比。 电机驱动:采用双H桥电路,控制电机的正反转和调速。 软件设计 编程语言:C语言。 开发环境:Keil uVision。 仿真工具:Proteus。 使用说明 液晶屏显示: 第一行显示电机转速(单位:转/分)。 第二行显示PWM占空比(0~100%)。 按键功能: 1键:加速键,短按占空比加1,长按连续加。 2键:减速键,短按占空比减1,长按连续减。 3键:反转切换键,按下后电机反转。 4键:正转切换键,按下后电机正转。 5键:开始暂停键,按一下开始,再按一下暂停。 注意事项 磁铁和霍尔元件的距离应保持在2mm左右,过近可能会在电机转动时碰到霍尔元件,过远则可能导致霍尔元件无法检测到磁铁。 资源文件 仿真文件:Proteus仿真文件,用于模拟电机控制系统的运行。 源程序:Keil uVision项目文件,包含完整的C语言源代码。 原理图:电路设计原理图,详细展示了各模块的连接方式。 PCB设计:PCB布局文件,可用于实际电路板的制作。
【四旋翼无人机】具备螺旋桨倾斜机构的全驱动四旋翼无人机:建模与控制研究(Matlab代码、Simulink仿真实现)内容概要:本文围绕具备螺旋桨倾斜机构的全驱动四旋翼无人机展开研究,重点进行了系统建模与控制策略的设计与仿真验证。通过引入螺旋桨倾斜机构,该无人机能够实现全向力矢量控制,从而具备更强的姿态调节能力和六自由度全驱动特性,克服传统四旋翼欠驱动限制。研究内容涵盖动力学建模、控制系统设计(如PID、MPC等)、Matlab/Simulink环境下的仿真验证,并可能涉及轨迹跟踪、抗干扰能力及稳定性分析,旨在提升无人机在复杂环境下的机动性与控制精度。; 适合人群:具备一定控制理论基础和Matlab/Simulink仿真能力的研究生、科研人员及从事无人机系统开发的工程师,尤其适合研究先进无人机控制算法的技术人员。; 使用场景及目标:①深入理解全驱动四旋翼无人机的动力学建模方法;②掌握基于Matlab/Simulink的无人机控制系统设计与仿真流程;③复现硕士论文级别的研究成果,为科研项目或学术论文提供技术支持与参考。; 阅读建议:建议结合提供的Matlab代码与Simulink模型进行实践操作,重点关注建模推导过程与控制器参数调优,同时可扩展研究不同控制算法的性能对比,以深化对全驱动系统控制机制的理解。
标题中的"EthernetIP-master.zip"压缩文档涉及工业自动化领域的以太网通信协议EtherNet/IP。该协议由罗克韦尔自动化公司基于TCP/IP技术架构开发,已广泛应用于ControlLogix系列控制设备。该压缩包内可能封装了协议实现代码、技术文档或测试工具等核心组件。 根据描述信息判断,该资源主要用于验证EtherNet/IP通信功能,可能包含测试用例、参数配置模板及故障诊断方案。标签系统通过多种拼写形式强化了协议主题标识,其中"swimo6q"字段需结合具体应用场景才能准确定义其技术含义。 从文件结构分析,该压缩包采用主分支命名规范,符合开源项目管理的基本特征。解压后预期可获取以下技术资料: 1. 项目说明文档:阐述开发目标、环境配置要求及授权条款 2. 核心算法源码:采用工业级编程语言实现的通信协议栈 3. 参数配置文件:预设网络地址、通信端口等连接参数 4. 自动化测试套件:包含协议一致性验证和性能基准测试 5. 技术参考手册:详细说明API接口规范与集成方法 6. 应用示范程序:展示设备数据交换的标准流程 7. 工程构建脚本:支持跨平台编译和部署流程 8. 法律声明文件:明确知识产权归属及使用限制 该测试平台可用于构建协议仿真环境,验证工业控制器与现场设备间的数据交互可靠性。在正式部署前开展此类测试,能够有效识别系统兼容性问题,提升工程实施质量。建议用户在解压文件后优先查阅许可协议,严格遵循技术文档的操作指引,同时需具备EtherNet/IP协议栈的基础知识以深入理解通信机制。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
本项目旨在展示如何在STM32F4系列微控制器上通过SPI接口使用FatFS库来实现对SD卡的读写操作。STM32F4是一款高性能的ARM Cortex-M4内核MCU,广泛应用于嵌入式系统开发。该项目已成功调试通过,适用于需要在STM32F4平台进行文件存储的应用场景。 硬件配置 微控制器:STM32F4XX系列 SPI接口配置: Chip Select (CS):GPIOB Pin 11 Serial Clock (SCLK):GPIOB Pin 13 Master In Slave Out (MISO):GPIOB Pin 14 Master Out Slave In (MOSI):GPIOB Pin 15 请确保硬件连接正确,并且外部SD卡已被格式化为兼容FatFS的文件系统(如FAT16或FAT32)。 软件框架 编译环境:建议使用Keil uVision或STM32CubeIDE等常见STM32开发环境。 FatFS版本:此示例基于特定版本的FatFS库,一个轻量级的文件系统模块,专为嵌入式系统设计。 驱动实现:包括了SPI总线驱动和FatFS的适配层,实现了对SD卡的基本读写操作函数。 主要功能 初始化SPI接口:设置SPI模式、时钟速度等参数。 FatFS初始化:挂载SD卡到文件系统。 文件操作:包括创建、打开、读取、写入和关闭文件。 错误处理:提供了基本的错误检查和处理逻辑。 使用指南 导入项目:将代码导入到你的开发环境中。 配置环境:根据你所使用的IDE调整必要的编译选项和路径。 硬件连接:按照上述硬件配置连接好STM32F4与SD卡。 编译并烧录:确保一切就绪后,编译代码并通过编程器将其烧录到STM32F4中。 测试运行:连接串口监控工具,观察输出以验证读写操作是否成功。 注意事项 在尝试修改或集成到其他项目前,请理解核心代码的工作原理和依赖关系。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值