docker搭建etcd集群

news2024/11/16 1:53:25

最近用到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

在这里插入图片描述

通过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集群

这里使用的是业界比较通用的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.coloradmin.cn/o/1201302.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

初始MySQL(四)(查询加强练习,多表查询(未完))

目录 查询加强 where加强 order by加强 group by 分页查询 总结 多表查询(重点) 笛卡尔集及其过滤 自连接 子查询 #先创建三张表 #第一张表 CREATE TABLE dept(deptno MEDIUMINT NOT NULL DEFAULT 0,dname VARCHAR(20) NOT NULL DEFAULT ,loc VARCHAR(13) NOT NULL D…

【KVM】硬件虚拟化技术(详)

前言 大家好&#xff0c;我是秋意零。 经过前面章节的介绍&#xff0c;已经知道KVM虚拟化必须依赖于硬件辅助的虚拟化技术&#xff0c;本节就来介绍一下硬件虚拟化技术。 &#x1f47f; 简介 &#x1f3e0; 个人主页&#xff1a; 秋意零&#x1f525; 账号&#xff1a;全平…

SpringBoot2.X整合集成Dubbo

环境安装 Dubbo使用zookeeper作为注册中心&#xff0c;首先要安装zookeeper。 Windows安装zookeeper如下&#xff1a; https://blog.csdn.net/qq_33316784/article/details/88563482 Linux安装zookeeper如下&#xff1a; https://www.cnblogs.com/expiator/p/9853378.html Sp…

CCF ChinaSoft 2023 论坛巡礼 | CCF-华为胡杨林基金-形式化方法专项(海报)论坛

2023年CCF中国软件大会&#xff08;CCF ChinaSoft 2023&#xff09;由CCF主办&#xff0c;CCF系统软件专委会、形式化方法专委会、软件工程专委会以及复旦大学联合承办&#xff0c;将于2023年12月1-3日在上海国际会议中心举行。 本次大会主题是“智能化软件创新推动数字经济与社…

从0到0.01入门React | 008.精选 React 面试题

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

Android 基本属性绘制文本对象FontMetrics

FontMetrics对象 它以四个基本坐标为基准&#xff0c;分别为&#xff1a; ・FontMetrics.top ・FontMetrics.ascent ・FontMetrics.descent ・FontMetrics.bottom 如图: 要点如下&#xff1a; 1. 基准点是baseline 2. Ascent是baseline之上至字符最高处的距离 3. Descent是ba…

【多线程 - 03、线程的生命周期】

生命周期 当线程被创建并启动以后&#xff0c;它不是一启动就进入执行状态&#xff0c;也不会一直处于执行状态&#xff0c;而是会经历五种状态。 线程状态的五个阶段&#xff1a; 新建状态&#xff08;New&#xff09;就绪状态&#xff08;Runnable&#xff09;运行状态&…

TensorFlow学习笔记--(3)张量的常用运算函数

损失函数及求偏导 通过 tf.GradientTape 函数来指定损失函数的变量以及表达式 最后通过 gradient(%损失函数%,%偏导对象%) 来获取求偏导的结果 独热编码 给出一组特征值 来对图像进行分类 可以用独热编码 0的概率是第0种 1的概率是第1种 0的概率是第二种 tf.one_hot(%某标签…

【差旅游记】启程-新疆哈密(1)

哈喽&#xff0c;大家好&#xff0c;我是雷工。 最近有个新疆罗布泊的项目要去现场&#xff0c;领导安排我过去&#xff0c;这也算第一次到新疆&#xff0c;记录下去新疆的过程。 01、天有不测风云 本来预定的是11月2号石家庄飞成都&#xff0c;成都转机到哈密&#xff0c;但…

为什么要用“交叉熵”做损失函数

大家好啊&#xff0c;我是董董灿。 今天看一个在深度学习中很枯燥但很重要的概念——交叉熵损失函数。 作为一种损失函数&#xff0c;它的重要作用便是可以将“预测值”和“真实值(标签)”进行对比&#xff0c;从而输出 loss 值&#xff0c;直到 loss 值收敛&#xff0c;可以…

Linux--gcc/g++

一、gcc/g是什么 gcc的全称是GNU Compiler Collection&#xff0c;它是一个能够编译多种语言的编译器。最开始gcc是作为C语言的编译器&#xff08;GNU C Compiler&#xff09;&#xff0c;现在除了c语言&#xff0c;还支持C、java、Pascal等语言。gcc支持多种硬件平台 二、gc…

【Pytorch和深度学习】栏目导读

一、栏目说明 本栏目《pytorch实践》是为初学者入门深度学习准备的。本文是该栏目的导读部分&#xff0c;因为计划本栏目在明年完成&#xff0c;因此&#xff0c;导读部分&#xff0c;即本文也在持续更新中。 本栏目设计目标是将深度学习全面用pytorch实践一遍&#xff0c;由浅…

2390 高校实验室预约系统JSP【程序源码+文档+调试运行】

摘要 本文介绍了一个高校实验室预约系统的设计和实现。该系统包括管理员、教师和学生三种用户&#xff0c;具有基础数据管理、学生管理、教师管理、系统公告管理、实验室管理、实验室预约管理和系统管理等模块。通过数据库设计和界面设计&#xff0c;实现了用户友好的操作体验…

std::any

一、简介 std::any 可以储存任何可拷贝构造和可销毁的类型的对象。 struct test {test(int a,int b){} };int main(int argc, char *argv[]) {std::any a 1;qDebug() << a.type().name();a 3.14;qDebug() << a.type().name();a true;qDebug() << a.type…

PyCharm 安装库时显示连接超时

在setting->python Interpreter 中用“” 安装库时&#xff0c;出现一个弹窗&#xff0c;提示信息如下&#xff1a; Error updating package list: Connect timed out 通过查阅资料&#xff0c;发现是镜像源的问题&#xff0c;具体的解决方案如下&#xff1a; 1. 更新一下…

阿里云国际站:云备份

文章目录 一、阿里云云备份的概念 二、云备份的优势 三、云备份的功能 四、云备份的应用场景 一、阿里云云备份的概念 云备份作为阿里云统一灾备平台&#xff0c;是一种简单易用、敏捷高效、安全可靠的公共云数据管理服务&#xff0c;可以为阿里云ECS整机、ECS数据库、文件…

代码分析之-广东省公共资源交易平台

广东省公共资源交易平台 hex: function Xq() {return bg || (bg 1,function(e, t) {(function(n, u) {e.exports u()})(an, function() {var n n || function(u, o) {var r;if (typeof window < "u" && window.crypto && (r window.crypto)…

[工业自动化-15]:西门子S7-15xxx编程 - 软件编程 - 硬件组态进行硬件配置与信号模块的分配、信号数据类型

目录 一、PLC组态在PLC编程中的位置&#xff1a;首要位置 1.1 什么是硬件组态 1.2 硬件组态在PLC编程中的位置 二、硬件组态的步骤&#xff1a; 三、信号模块的分配 3.1 what什么是PLC信号模块的地址&#xff0c;以及信号模地址的格式&#xff1f; 3.2 whyPLC信号模块地…

“苹果定律”失效,2023是VR的劫点还是拐点?

因为Pico裁员的事情&#xff0c;VR行业又被讨论了。 Pico于2021年9月被字节跳动收购&#xff0c;当时是出货量排名全球第三的VR 头显生产商。 此前曾有国际机构预测&#xff0c;2023年随着Meta和Pico的硬件更新&#xff0c;苹果Vision Pro的推出&#xff0c;三星电子重新回归VR…

[工业自动化-17]:西门子S7-15xxx编程 - 软件编程 - PLC编程语言以及与嵌入式编程的比较

目录 一、博图编程语言 1.1 概述 1.2 三种编程语言之间的关系 二、PLC与嵌入式系统的类比 三、PLC编程与嵌入式系统编程的比较 3.1 不同点 3.2 相同点 3.3 PLC是一种专门用于工业控制系统的嵌入式系统 一、博图编程语言 1.1 概述 西门子&#xff08;Siemens&#xff0…