记录 - k8s入门2 - helloredis (1.16.0)

本文详细介绍了如何在Kubernetes环境中部署一个包含一主两从的Redis集群,并通过Go语言编写的应用程序进行读写操作。文章涵盖了部署过程、镜像选择、服务创建以及应用程序的开发、编译和部署。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

环境

本篇改编自官方guestbook教程 (https://kubernetes.io/docs/tutorials/stateless-application/guestbook):redis一主双从 + phpweb。web应用改用golang编写以减小镜像体积方便试验。

本篇基于前一篇 记录 - k8s 入门搭建 (1.16.0, helloweb) 搭建的环境。主节点k8s0=192.168.199.200,工作节点k8s1=192.168.199.201。

启动redis master

helloredis应用设置数据到redis主节点,从redis从节点读取数据。

创建deploy

##以下在k8s0

mkdir -p /test/k8s/helloredis
cd /test/k8s/helloredis
vi redis-master-deployment.yaml

apiVersion: apps/v1  # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: redis-master
  labels:
    app: redis
spec:
  selector:
    matchLabels:
      app: redis
      role: master
      tier: backend
  replicas: 1
  template:
    metadata:
      labels:
        app: redis
        role: master
        tier: backend
    spec:
      containers:
      - name: master
        # image: k8s.gcr.io/redis:e2e  # or just image: redis
        image: redis:3.2.9
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
        ports:
        - containerPort: 6379

#注:redis的版本“3.2.9” 参考 [问题 - redis 不能主从复制]

#部署:
alias kc=kubectl
kc apply -f redis-master-deployment.yaml

kc get pod -o wide

NAME                            READY   STATUS    RESTARTS   AGE   IP               NODE   NOMINATED NODE   READINESS GATES
redis-master-8556bd886d-nthc4   1/1     Running   0          95s   10.100.166.254   k8s1   <none>           <none>

#查询pod日志
kc logs -f redis-master-8556bd886d-nthc4

1:M 06 Nov 07:53:54.349 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
1:M 06 Nov 07:53:54.349 # Server started, Redis version 3.2.9
1:M 06 Nov 07:53:54.349 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
1:M 06 Nov 07:53:54.349 * The server is now ready to accept connections on port 6379

创建service

vi redis-master-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: redis-master
  labels:
    app: redis
    role: master
    tier: backend
spec:
  ports:
  - port: 6379
    targetPort: 6379
  selector:
    app: redis
    role: master
    tier: backend

#“spec.selector” 指定目标pod 的标签,这些对应deploy 创建的pod:
kc describe pod/redis-master-8556bd886d-nthc4

Name:         redis-master-8556bd886d-nthc4
......
Labels:       app=redis
              pod-template-hash=8556bd886d
              role=master
              tier=backend

#部署
kc apply -f redis-master-service.yaml

kc get service -o wide

NAME           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE     SELECTOR
kubernetes     ClusterIP   10.96.0.1       <none>        443/TCP    4d21h   <none>
redis-master   ClusterIP   10.96.151.116   <none>        6379/TCP   9s      app=redis,role=master,tier=backend

#注:service host 在集群内可以通过dns或env 获取。

启动redis slave

虽然redis主只有一个pod,但可以通过配置redis从来提升HA。

创建deploy

#下面yaml里配置了2个replica,当apply时如果当时已经有多于2个pod,会自动删除多余的;反之如果当时还不足2个,会自动补充创建。

vi redis-slave-deployment.yaml

apiVersion: apps/v1  # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: redis-slave
  labels:
    app: redis
spec:
  selector:
    matchLabels:
      app: redis
      role: slave
      tier: backend
  replicas: 2
  template:
    metadata:
      labels:
        app: redis
        role: slave
        tier: backend
    spec:
      containers:
      - name: slave
        image: gcr.io/google_samples/gb-redisslave:v3
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
        env:
        - name: GET_HOSTS_FROM
          value: dns
          # value: env
        ports:
        - containerPort: 6379

#注:k8s 1.16内置dns,可以通过dns连接redis-slave。如果需要改用环境变量方式获取redis-slave host,将上面的配置"value: dns" 改为"value: env"。

#部署
kc apply -f redis-slave-deployment.yaml

kc get pod -o wide

NAME                            READY   STATUS    RESTARTS   AGE   IP               NODE   NOMINATED NODE   READINESS GATES
redis-master-8556bd886d-nthc4   1/1     Running   0          30m   10.100.166.254   k8s1   <none>           <none>
redis-slave-7664787fbc-r4plx    1/1     Running   0          19s   10.100.166.255   k8s1   <none>           <none>
redis-slave-7664787fbc-vfx6t    1/1     Running   0          19s   10.100.166.193   k8s1   <none>           <none>

#看到按照yaml的配置只创建了2个pod。

#可以直接curl redis pod:

# curl 10.100.166.255:6379
-ERR wrong number of arguments for 'get' command
-ERR unknown command 'User-Agent:'

关于redis slave镜像

参考 https://github.com/kubernetes/examples/blob/master/guestbook/redis-slave/run.sh

if [[ ${GET_HOSTS_FROM:-dns} == "env" ]]; then
  redis-server --slaveof ${REDIS_MASTER_SERVICE_HOST} 6379
else
  redis-server --slaveof redis-master 6379
fi

“REDIS_MASTER_SERVICE_HOST” 环境变量由k8s自动设置,参考 https://kubernetes.io/docs/concepts/services-networking/connect-applications-service/

创建service

#在集群内不应直连某个pod,因为pod 可以动态创建/删除/移动。应该创建一个service,由service动态路由到pod。

vi redis-slave-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: redis-slave
  labels:
    app: redis
    role: slave
    tier: backend
spec:
  ports:
  - port: 6379
  selector:
    app: redis
    role: slave
    tier: backend

#部署
kc apply -f redis-slave-service.yaml

kc get service -o wide

NAME           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE     SELECTOR
kubernetes     ClusterIP   10.96.0.1       <none>        443/TCP    4d22h   <none>
redis-master   ClusterIP   10.96.151.116   <none>        6379/TCP   32m     app=redis,role=master,tier=backend
redis-slave    ClusterIP   10.96.146.129   <none>        6379/TCP   22s     app=redis,role=slave,tier=backend
# curl 10.96.146.129:6379
-ERR wrong number of arguments for 'get' command
-ERR unknown command 'User-Agent:'

helloredis 编写、编译

hello_redis.go

参考 https://github.com/kubernetes/examples/blob/master/guestbook/php-redis/guestbook.php

封装调用redis get、set 两个方法的http api,在80端口监听:

package main

import (
    "bufio"
    "fmt"
    "io"
    "net"
    "net/http"
    "os"
    "strconv"
)

func main() {
    http.Handle("/get", http.HandlerFunc(handle_get))
    http.Handle("/set", http.HandlerFunc(handle_set))

    if err := http.ListenAndServe(":80", nil); err != nil {
        fmt.Println("ListenAndServe:", err)
    }
}

func handle_get(w http.ResponseWriter, req *http.Request) {
    str, err := get()
    if err != nil {
        fmt.Fprintln(w, "Error:", err)
    } else {
        fmt.Fprintln(w, "Got:", str)
    }
}

func handle_set(w http.ResponseWriter, req *http.Request) {
    value := req.FormValue("value")
    str, err := set(value)
    if err != nil {
        fmt.Fprintln(w, "Error:", err)
    } else {
        fmt.Fprintln(w, "Got:", str)
    }
}

redis的协议其实比较简单,可网上搜索“redis 通讯协议”。自行实现get方法:

func get() (string, error) {
    host := "redis-slave"
    if os.Getenv("GET_HOSTS_FROM") == "env" {
        host = os.Getenv("REDIS_SLAVE_SERVICE_HOST")
    }

    conn, err := net.Dial("tcp", host + ":6379")
    if err != nil {
        return "", err
    }

    defer conn.Close()

    // GET key
    fmt.Fprint(conn, "*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n")

    // 响应:$-1\r\n 或 $1\r\n1\r\n
    reader := bufio.NewReader(conn)
    str, err := reader.ReadString('\n')
    if err != nil {
        return "", err
    }

    lenstr := str[1:len(str)-2]
    len, _ := strconv.Atoi(lenstr)
    if len == -1 {
        return "", nil  // 没有值
    }

    buf := make([]byte, len)
    _, err = io.ReadFull(reader, buf)
    if err != nil {
        return "", err
    }

    return string(buf), nil
}

上面使用dns或环境变量值来连接redis从。

set方法类似,但连接redis主:

func set(value string) (string, error) {
    host := "redis-master"
    if os.Getenv("GET_HOSTS_FROM") == "env" {
        host = os.Getenv("REDIS_MASTER_SERVICE_HOST")
    }

    conn, err := net.Dial("tcp", host + ":6379")
    if err != nil {
        return "", err
    }

    defer conn.Close()

    // SET key value
    fmt.Fprintf(conn, "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$%d\r\n%s\r\n", len(value), value)

    // 响应:+OK\r\n
    reader := bufio.NewReader(conn)
    str, err := reader.ReadString('\n')
    if err != nil {
        return "", err
    }

    return str[1:len(str)-2], nil
}

编译为可执行文件 hello_redis (7.2M)。

编译、上传镜像

mkdir -p /test/k8s/docker_redis
cd /test/k8s/docker_redis
vi Dockerfile

FROM alpine
WORKDIR /helloredis
ADD . /helloredis
RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2
EXPOSE 80
ENTRYPOINT ["./hello_redis"]

#将hello_redis 拷过来,目录下有2个文件:Dockerfile hello_redis
chmod +x hello_redis
#编译镜像。注意:“YYY” 需替换为你的hub.docker.com 账户名
docker build -t YYY/helloredis:v0.1 .

#再按照上一篇的做法将镜像上传到hub.docker.com

启动helloredis 应用

创建deploy

cd /test/k8s/helloredis
vi frontend-deployment.yaml

apiVersion: apps/v1  # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: frontend
  labels:
    app: helloredis
spec:
  selector:
    matchLabels:
      app: helloredis
      tier: frontend
  replicas: 3
  template:
    metadata:
      labels:
        app: helloredis
        tier: frontend
    spec:
      containers:
      - name: go-redis
        # image: gcr.io/google-samples/gb-frontend:v4
        image: YYY/helloredis:v0.1
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
        env:
        - name: GET_HOSTS_FROM
          value: dns
          # value: env
        ports:
        - containerPort: 80

#注:同样,上面“YYY”实际是docker hub的账户名

#部署
kc apply -f frontend-deployment.yaml

#观察deploy “frontend” 直到Ready=3/3
watch kubectl get deploy

#查询3个pod
kc get pods -l app=helloredis -l tier=frontend -o wide

NAME                        READY   STATUS    RESTARTS   AGE     IP               NODE   NOMINATED NODE   READINESS GATES
frontend-868f999cb9-c2kb9   1/1     Running   0          6m51s   10.100.166.195   k8s1   <none>           <none>
frontend-868f999cb9-gmqzc   1/1     Running   0          6m51s   10.100.166.194   k8s1   <none>           <none>
frontend-868f999cb9-gzghz   1/1     Running   0          6m51s   10.100.166.197   k8s1   <none>           <none>

#直接选一个pod试验下get/set接口

[root@k8s0 helloredis]# curl 10.100.166.195/get
Got: 
[root@k8s0 helloredis]# curl 10.100.166.195/set?value=Holly
Got: OK
[root@k8s0 helloredis]# curl 10.100.166.195/get
Got: Holly
[root@k8s0 helloredis]# curl 10.100.166.197/get
Got: Holly

#可见主从读写正常

创建service

#默认"redis-master"和"redis-slave" service 都只能在集群内部访问 (类型"ClusterIP")。为了能从集群外部访问helloredis应用,将其类型设为"NodePort" (类型"LoadBalancer" 只在云厂商环境下可用)。

vi frontend-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: frontend
  labels:
    app: helloredis
    tier: frontend
spec:
  type: NodePort
  # type: LoadBalancer
  ports:
  - port: 80
  selector:
    app: helloredis
    tier: frontend

#部署
kc apply -f frontend-service.yaml

kc get service -o wide

NAME           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE     SELECTOR
frontend       NodePort    10.96.214.32    <none>        80:30418/TCP   4s      app=helloredis,tier=frontend
kubernetes     ClusterIP   10.96.0.1       <none>        443/TCP        5d1h    <none>
redis-master   ClusterIP   10.96.151.116   <none>        6379/TCP       3h50m   app=redis,role=master,tier=backend
redis-slave    ClusterIP   10.96.146.129   <none>        6379/TCP       3h18m   app=redis,role=slave,tier=backend

#请求一下

[root@k8s0 helloredis]# curl 10.96.214.32:80/get
Got: Holly
[root@k8s0 helloredis]# curl 192.168.199.200:30418/get
Got: Holly

#注:在k8s1机器也可

#顺便验证下 host

[root@k8s0 helloredis]# kc exec pod/frontend-868f999cb9-gzghz -it /bin/sh
/helloredis # env | grep "SERVICE_HOST"
REDIS_SLAVE_SERVICE_HOST=10.96.146.129
REDIS_MASTER_SERVICE_HOST=10.96.151.116
KUBERNETES_SERVICE_HOST=10.96.0.1
/helloredis # ping redis-master
PING redis-master (10.96.151.116): 56 data bytes
......
/helloredis # exit

伸缩部署

kc scale deploy frontend --replicas=5

kc scale deploy frontend --replicas=2

kc get pod

NAME                           READY   STATUS    RESTARTS   AGE
frontend-868f999cb9-c2kb9      1/1     Running   0          90m
frontend-868f999cb9-gzghz      1/1     Running   0          90m
redis-master-98f9cfc96-xhzr9   1/1     Running   0          50m
redis-slave-7664787fbc-r4plx   1/1     Running   0          3h52m
redis-slave-7664787fbc-vfx6t   1/1     Running   0          3h52m

清理

kc delete deploy -l app=redis
kc delete svc -l app=redis
kc delete deploy -l app=helloredis
kc delete svc -l app=helloredis

kc get pod

No resources found in default namespace.

补充

虚机重启后在kubernetes完成启动前“kc get node”会报错:
Unable to connect to the server: net/http: TLS handshake timeout

The connection to the server apiserver.demo:6443 was refused - did you specify the right host or port?

修改应用重新打包镜像时如果镜像名称和版本 都不变,容器拉取的可能还是老的镜像(待确认)。

问题

redis 不能主从复制

描述====

原文 redis-master-deployment.yaml 里“image: k8s.gcr.io/redis:e2e” - 这个镜像有419M,所以最初改成“image: redis”。
部署master、slave后,发现master写入后不能从slave读到。

诊断====

#查询slave日志
kc logs deploy/redis-slave

8:S 06 Nov 04:08:42.009 * Connecting to MASTER redis-master:6379
8:S 06 Nov 04:08:42.013 * MASTER <-> SLAVE sync started
8:S 06 Nov 04:08:42.013 * Non blocking connect for SYNC fired the event.
8:S 06 Nov 04:08:42.013 * Master replied to PING, replication can continue...
8:S 06 Nov 04:08:42.014 * Partial resynchronization not possible (no cached master)
8:S 06 Nov 04:08:42.015 * Full resync from master: 68398c71799a8917a8902e371de8654483978bd4:812
8:S 06 Nov 04:08:42.110 * MASTER <-> SLAVE sync: receiving 176 bytes from master
8:S 06 Nov 04:08:42.110 * MASTER <-> SLAVE sync: Flushing old data
8:S 06 Nov 04:08:42.110 * MASTER <-> SLAVE sync: Loading DB in memory
8:S 06 Nov 04:08:42.110 # Can't handle RDB format version 9
8:S 06 Nov 04:08:42.110 # Failed trying to load the MASTER synchronization DB from disk

日志表明从同步到主的数据后不能处理RDB 9格式,所以可能是主、从版本不兼容。

slave是“image: gcr.io/google_samples/gb-redisslave:v3”。
进入一个slave pod后查询版本:

[root@k8s0 helloredis]# kc exec pod/redis-slave-7664787fbc-r4plx -it /bin/sh
# redis-cli --version
redis-cli 3.2.9
# exit

同样进入master pod查询版本发现是redis-cli 5.0.6。主的版本过高。

修复====
改为"redis:3.2.9",重新apply后,查询日志:

8:S 06 Nov 06:41:46.511 * MASTER <-> SLAVE sync: receiving 76 bytes from master
8:S 06 Nov 06:41:46.511 * MASTER <-> SLAVE sync: Flushing old data
8:S 06 Nov 06:41:46.511 * MASTER <-> SLAVE sync: Loading DB in memory
8:S 06 Nov 06:41:46.511 * MASTER <-> SLAVE sync: Finished with success
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值