docker搭建etcd集群

news/2024/5/18 21:51:22 标签: docker, etcd, 容器, prometheus, etcdkeeper

最近用到etcd,就打算用docker搭建一套,学习整理了一下。记录在此,抛砖引玉。

文中的配置、代码见于https://gitee.com/bbjg001/darcy_common/tree/master/docker_compose_etcd

搭建一个单节点

docker run -d --name etcdx \
    -p 2379:2379 \
    -p 2380:2380 \
    -e ALLOW_NONE_AUTHENTICATION=yes \
    -e ETCD_ADVERTISE_CLIENT_URLS=http://etcdx:2379 \
    bitnami/etcd:3.5.0

这样一个单节点etcd就起来了,环境变量ALLOW_NONE_AUTHENTICATION=yes允许无需密码登录

如果要设置密码,需要设置环境变量ETCD_ROOT_PASSWORD=xxxxxx,否则容器无法启动起来

test it,用etcdctl操作etcd

docker exec -it etcdx bash
etcdctl put name zhangsan
etcdctl get name

在这里插入图片描述

dockercomposeetcd_31">通过docker-compose搭建etcd集群

docker-compose配置文件如下

# docker-compose.cluster.yml
version: "3.0"

networks:
  etcd-net:           # 网络
    name: etcd-net
    driver: bridge    # 桥接模式
    ipam:
      driver: default
      config:
        - subnet: 192.168.23.0/24
          gateway: 192.168.23.1

volumes:
  etcd1_data:         # 挂载到本地的数据卷名
    driver: local
  etcd2_data:
    driver: local
  etcd3_data:
    driver: local

# etcd 其他环境配置见:https://doczhcn.gitbook.io/etcd/index/index-1/configuration
services:
  etcd1:
    image: bitnami/etcd:3.5.0  # 镜像
    container_name: etcd1       # 容器名 --name
    restart: always             # 总是重启
    networks:
      - etcd-net                # 使用的网络 --network
    ports:                      # 端口映射 -p
      - "20079:2379"
      - "20080:2380"
    environment:                # 环境变量 --env
      - ALLOW_NONE_AUTHENTICATION=yes                       # 允许不用密码登录
      - ETCD_NAME=etcd1                                     # etcd 的名字
      - ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd1:2380  # 列出这个成员的伙伴 URL 以便通告给集群的其他成员
      - ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380           # 用于监听伙伴通讯的URL列表
      - ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379         # 用于监听客户端通讯的URL列表
      - ETCD_ADVERTISE_CLIENT_URLS=http://etcd1:2379        # 列出这个成员的客户端URL,通告给集群中的其他成员
      - ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster             # 在启动期间用于 etcd 集群的初始化集群记号
      - ETCD_INITIAL_CLUSTER=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380 # 为启动初始化集群配置
      - ETCD_INITIAL_CLUSTER_STATE=new                      # 初始化集群状态
    volumes:
      - etcd1_data:/bitnami/etcd                            # 挂载的数据卷

  etcd2:
    image: bitnami/etcd:3.5.0
    container_name: etcd2
    restart: always
    networks:
      - etcd-net
    ports:
      - "20179:2379"
      - "20180:2380"
    environment:
      - ALLOW_NONE_AUTHENTICATION=yes
      - ETCD_NAME=etcd2
      - ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd2:2380
      - ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380
      - ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
      - ETCD_ADVERTISE_CLIENT_URLS=http://etcd2:2379
      - ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster
      - ETCD_INITIAL_CLUSTER=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
      - ETCD_INITIAL_CLUSTER_STATE=new
    volumes:
      - etcd2_data:/bitnami/etcd

  etcd3:
    image: bitnami/etcd:3.5.0
    container_name: etcd3
    restart: always
    networks:
      - etcd-net
    ports:
      - "20279:2379"
      - "20280:2380"
    environment:
      - ALLOW_NONE_AUTHENTICATION=yes
      - ETCD_NAME=etcd3
      - ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd3:2380
      - ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380
      - ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
      - ETCD_ADVERTISE_CLIENT_URLS=http://etcd3:2379
      - ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster
      - ETCD_INITIAL_CLUSTER=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
      - ETCD_INITIAL_CLUSTER_STATE=new
    volumes:
      - etcd3_data:/bitnami/etcd

  docker-etcdkeeper:
    hostname: etcdkeeper
    container_name: etcdkeeper
    image: evildecay/etcdkeeper
    ports:
      - "28080:8080"
    networks:
      - etcd-net
    depends_on:
      - etcd1
      - etcd2
      - etcd3

启动

docker-compose -f docker-compose.cluster.yml up
docker-compose -f docker-compose.cluster.yml up -d	# -d 参数可以不在前台输出日志
# 注意如果修改了docker-compose配置文件重新启动,需要先清理掉当前集群
docker-compose -f docker-compose.cluster.yml down		# 相当于docker rm
docker-compose -f docker-compose.cluster.yml down	-v	# 清理volume

test it,

查看etcd状态

etcdctl --endpoints="192.168.9.109:20079,192.168.9.109:20179,192.168.9.109:20279" --write-out=table endpoint health
# --write-out 可以控制返回值的格式,可选table、json等
etcdctl --endpoints="192.168.9.109:20079,192.168.9.109:20179,192.168.9.109:20279" --write-out=table endpoint status

在这里插入图片描述

在这个集群中顺便启动了一个etcdkeeper,etcdkeeper是一个轻量级的etcd web客户端,支持etcd 2.x和etcd3.x。

在上面的配置文件中,etcdkeeper服务映射给了物理机的28080端口,在浏览器访问etcdkeeper http://192.168.9.109:28080/etcdkeeper/,其中192.168.9.109是物理机的ip

在这里插入图片描述

etcd_172">带监控的etcd集群

这里使用的是业界比较通用的Prometheus方案,简单使用可以浅看一下Prometheus容器状态监控,其大概逻辑是数据源(metrics接口)->Prometheus->Grafana,在当前场景中,数据源是etcd,它提供了metrics接口(http://192.168.9.109:20079/metrics)

etcd集群还是像上一节相同的配置,另外增加了启动Prometheus和Grafana的配置如下

  prometheus:
    image: prom/prometheus
    container_name: prometheus
    hostname: prometheus
    restart: always
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "29090:9090"
    networks:
      - etcd-net

  grafana:
    image: grafana/grafana
    container_name: grafana
    hostname: grafana
    restart: always
    ports:
      - "23000:3000"
    networks:
      - etcd-net

在Prometheus的配置文件中启动需要配置的数据源,在这里就是etcd的入口

# prometheus.yml
scrape_configs:
  - job_name: 'etcd'
    static_configs:
    - targets: [ '192.168.9.109:20079','192.168.9.109:20179,','192.168.9.109:20279,' ]

依旧如上启动

# 先把上一个集群清理掉
#  docker-compose -f docker-compose.cluster.yml down
#  docker-compose -f docker-compose.cluster.yml down -v
docker-compose -f docker-compose.monitor.yml up

然后访问Grafana的地址(http://192.168.9.109:23000)进行配置

添加datasource

在这里插入图片描述

add data source后选择Prometheus

在这里插入图片描述

只填写启动的Prometheus的地址就可以,然后保存,看到这个datasource是working的状态

在这里插入图片描述

添加Dashboard。这里我们直接导入已有的dashboard

在这里插入图片描述

可以选择3070,9733等

在这里插入图片描述

数据源选择刚才配置的Prometheus

在这里插入图片描述

为了让曲线有浮动,写了个小脚本访问一下etcd(这不重要)

package main

// 通过client多个协程公用
import (
	"context"
	"flag"
	"fmt"
	"log"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"

	"go.etcd.io/etcd/clientv3"
)

const (
	timeGapMs = 0
)

var (
	concurrency         int    = 2
	mode                string = "write"
	timeDurationSeconds int64  = 60
	endSecond           int64  = 0
)

func initParams() {
	flag.IntVar(&concurrency, "c", concurrency, "并发数")
	flag.Int64Var(&timeDurationSeconds, "t", timeDurationSeconds, "运行持续时间")
	flag.StringVar(&mode, "m", mode, "stress mode, write/rdonly/rw")
	// 解析参数
	flag.Parse()
	// log.Println("Args: ", flag.Args())
}

func GoID() int {
	var buf [64]byte
	n := runtime.Stack(buf[:], false)
	idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0]
	id, err := strconv.Atoi(idField)
	if err != nil {
		panic(fmt.Sprintf("cannot get goroutine id: %v", err))
	}
	return id
}

func writeGoroutines(cli *clientv3.Client, ch chan int64, wg *sync.WaitGroup) {
	defer wg.Done()
	log.Printf("write, goid: %d", GoID())
	num := int64(0)
	for {
		timeUnixNano := time.Now().UnixNano()
		key := strconv.FormatInt(timeUnixNano/1000%1000000, 10)
		value := strconv.FormatInt(timeUnixNano, 10)
		// fmt.Printf("Put, %s:%s\n", key, value)
		if _, err := cli.Put(context.Background(), key, value); err != nil {
			log.Fatal(err)
		}
		num++
		if time.Now().Unix() > endSecond {
			break
		}
		time.Sleep(time.Millisecond * timeGapMs)
	}
	ch <- num
	// done <- true
}

func readGoroutines(cli *clientv3.Client, ch chan int64, wg *sync.WaitGroup) {
	defer wg.Done()
	log.Printf("read, goid: %d", GoID())
	num := int64(0)
	for {
		timeUnixNano := time.Now().UnixNano()
		key := strconv.FormatInt(timeUnixNano/1000%1000000, 10)
		resp, err := cli.Get(context.Background(), key)
		if err != nil {
			log.Fatal(err)
		}
		if len(resp.Kvs) == 0 {
			// fmt.Printf("Get, key=%s not exist\n", key)
		} else {
			// for _, ev := range resp.Kvs {
			// 	log.Printf("Get, %s:%s\n", string(ev.Key), string(ev.Value))
			// }
		}
		num++
		if time.Now().Unix() > endSecond {
			break
		}
		time.Sleep(time.Millisecond * timeGapMs)
	}
	ch <- num
	// done <- true
}

func init() {
	initParams()
	log.SetFlags(log.Lshortfile)
}

func main() {

	// 创建ETCD客户端
	cli, err := clientv3.New(clientv3.Config{
		Endpoints: []string{"10.23.171.86:20079", "10.23.171.86:20179", "10.23.171.86:20279"}, // ETCD服务器地址
		// Endpoints:   []string{"192.168.9.103:2379"}, // ETCD服务器地址
		DialTimeout: 5 * time.Second,
	})
	if err != nil {
		log.Fatal(err)
	}
	defer cli.Close()

	// doneCh := make(chan bool, concurrency)
	var wg sync.WaitGroup
	writeCh := make(chan int64, concurrency)
	readCh := make(chan int64, concurrency)
	endSecond = time.Now().Unix() + timeDurationSeconds

	for i := 0; i < concurrency; i++ {
		wg.Add(1)
		if i%2 == 0 {
			// 启动写入goroutine
			go writeGoroutines(cli, writeCh, &wg)
		} else {
			// 启动读取goroutine
			go readGoroutines(cli, readCh, &wg)
		}
	}

	// 等待所有goroutine完成
	// <-doneCh
	wg.Wait()

	num_w := int64(0)
	num_r := int64(0)
	close(writeCh)
	for wi := range writeCh {
		// fmt.Println(wi)
		num_w += wi
	}
	close(readCh)
	for ri := range readCh {
		// fmt.Println("read", ri)
		num_r += ri
	}
	fmt.Printf("write total: %d, time: %d, qps: %d\n", num_w, timeDurationSeconds, num_w/timeDurationSeconds)
	fmt.Printf("read total: %d, time: %d, qps: %d\n", num_r, timeDurationSeconds, num_r/timeDurationSeconds)
}

go mod init ectdopt
# 避开依赖报错做下面两行replace
go mod edit -replace github.com/coreos/bbolt=go.etcd.io/bbolt@v1.3.4
go mod edit -replace google.golang.org/grpc=google.golang.org/grpc@v1.26.0
go mod tidy
go run request_etcd.go

监控效果

在这里插入图片描述

另外说

  • etcd通过https对外暴露服务时,Prometheus采集数据指标需要使用TLS证书

参考

etcd 其他环境配置见:https://doczhcn.gitbook.io/etcd/index/index-1/configuration

https://sakishum.com/2021/11/18/docker-compose%E9%83%A8%E7%BD%B2ETCD/

http://www.mydlq.club/article/117/

https://kenkao.blog.csdn.net/article/details/125083564


http://www.niftyadmin.cn/n/5175058.html

相关文章

MySQL主从环境搭建

MySQL主从环境搭建 主机MySQL配置 修改主机配置文件 vim /etc/my.cnf主要是在my.cnf配置文件中增加以下内容&#xff1a; #主服务器唯一ID server-id1 #启用二进制日志 log-binmysql-bin # 设置不要复制的数据库(可设置多个) binlog-ignore-dbmysql binlog-ignore-dbinform…

BGP属性实验

一、实验拓扑 二、实验要求 按照图示配置IP地址以及在路由器上配置BGP&#xff0c;使其全网通 1、配置IP地址 2、配置AS 200内的OSPF [AR2]ospf 1 router-id 2.2.2.2 [AR2-ospf-1]a 0 [AR2-ospf-1-area-0.0.0.0]network 2.2.2.2 0.0.0.0 [AR2-ospf-1-area-0.0.0.0]network 1…

Vue组件传

跟禹神学vue--总结 1 父组件给子组件传递参数--props传参 &#xff08;1&#xff09;父组件中准备好数据 data() {return {todos:[{id:001,title:01,done:true},{id:002,title:02,done:false},{id:003,title:03,done:true}]} } &#xff08;2&#xff09;父组件中引入子组件…

苍穹外卖项目学习日记(12)

苍穹外卖项目学习日记(12) day09 历史订单查询 controller层OrderController添加历史订单查询方法OrderController.java GetMapping("/historyOrders")ApiOperation("历史订单查询")public Result<PageResult> page(OrdersPageQueryDTO ordersPage…

Apache配置ssl证书-实现https访问

文章目录 一、准备工作1.1 安装Apache服务器1.2 Apache服务器上已经开启了443端口1.3 Apache服务器上已安装了mod_ssl.so模块1.4 获取SSL证书 二、配置apache2.1 配置apache文件2.2 生效配置文件 一、准备工作 1.1 安装Apache服务器 yum install httpd -y1.2 Apache服务器上已…

Spring-ProxyFactory

ProxyFactory选择cglib或jdk动态代理原理 ProxyFactory在生成代理对象之前需要决定是使用JDK动态代理还是CGLIB技术&#xff1a; public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {Overridepublic AopProxy createAopProxy(AdvisedSupport co…

戴德金分割构造实数

戴德金分割 具有最小上界性的有序域 R \mathbb{R} R存在 此外&#xff0c; R \mathbb{R} R包容着 Q \mathbb{Q} Q作为其子域。 第二句话表示 Q ⊂ R \mathbb{Q}\subset \mathbb{R} Q⊂R而且把 R \mathbb{R} R中的假发与乘法运算用于 Q \mathbb{Q} Q的元时&#xff0c;与有理数…

IP多播需要使用两种协议(IGMP和多播路由选择协议)

目录 IGMP 多播路由选择协议 组播协议包括组成员管理协议和组播路由协议: 组成员管理协议用于管理组播组成员的加入和离开(IGMP) 组播路由协议负责在路由器之间交互信息来建立组播树(多播路由选择协议) IGMP 图中标有 IP 地址的四台主机都参加了一个多播组&#xff0c;其…