Etcd本地部署

Linux

数据源选择${DOWNLOAD_URL}:
发布版本${ETCD_VER}:
下载压缩包指令
wget -c ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -P /tmp

#参数 -c 断点续传
#	 -P 指定目录下载

#例如:wget -c https://github.com/etcd-io/etcd/releases/download/v3.5.0/etcd-v3.5.0-linux-amd64.tar.gz -P /tmp
解压缩gzip指令

压缩包解压出来有用的就只有etcd、etcdutl、etcdctl,其它均是文档

  • etcdctl: etcd的命令行式客户端

  • etcdutl:etcd的命令行式系统工具集

      tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /tmp --strip-components=1
      
      #参数:-C 改变目录
      #	 --strip-components 删除的前导组件数量
将etcd、etcdctl、etcdutl移至/usr/local/bin
mv /tmp/etcd /usr/local/bin
mv /tmp/etcdctl /usr/local/bin
mv /tmp/etcdutl /usr/local/bin

启动Etcd

前台启动
etcd #默认端口号是2379 
etcd localhost:XXXX #指定端口号
后台启动
nohup etcd &  #缺省输出日志文件,则会把所有输出都重定向到nohup.out文件中
nohup etcd >/tmp/etcd-log/etcd.log 2>&1 & #指定输出日志文件 并将标准输出和错误输出都定向到etcd.log
nohup etcd -data-dir=/tmp/etcd_data_dir >/tmp/etcd-log/etcd.log 2>&1 & #指定etcd存储库的目录,默认在data_dir目录下生成default.etcd,可指定存储目录名/tmp/etcd_data_dir/xxxx.etcd

Etcd的简单使用

etcdctl put foo bar #输出OK
etcdctl get foo #输出bar

脚本部署Etcd集群

三节点集群

node_nameipport
etcd_1127.0.0.12379,2380
etcd_2127.0.0.12479,2480
etcd_3127.0.0.12579,2580
start_etcd1.sh
#!/bin/bash

TOKEN=token-cluster
CLUSTER_STATE=new
NAME_1=etcd_1
NAME_2=etcd_2
NAME_3=etcd_3
HOST_1=127.0.0.1
HOST_2=127.0.0.1
HOST_3=127.0.0.1
PORT_API_1=2379
PORT_PEER_1=2380
PORT_API_2=2479
PORT_PEER_2=2480
PORT_API_3=2579
PORT_PEER_3=2580

CLUSTER=${NAME_1}=http://${HOST_1}:${PORT_PEER_1},${NAME_2}=http://${HOST_2}:${PORT_PEER_2},${NAME_3}=http://${HOST_3}:${PORT_PEER_3}

# For every machine
THIS_NAME=${NAME_1}
THIS_IP=${HOST_1}
THIS_PORT_API=${PORT_API_1}
THIS_PORT_PEER=${PORT_PEER_1}

etcd --data-dir=/tmp/etcd_data_conf/${THIS_NAME}/data_dir \  #指定节点的数据存储目录
	 --name ${THIS_NAME} \   #节点名
     --initial-advertise-peer-urls http://${THIS_IP}:${THIS_PORT_PEER} \  #用于向集群里其它节点告知本节点用于与其它节点通讯的url列表
     --listen-peer-urls http://${THIS_IP}:${THIS_PORT_PEER} \ #监听URL,用于与其它节点通讯
     --advertise-client-urls http://${THIS_IP}:${THIS_PORT_API} \ # 用于向集群里的其它成员告知本节点的客户端url列表
     --listen-client-urls http://${THIS_IP}:${THIS_PORT_API} \ # 用于监听客户端流量的url列表
     --initial-cluster ${CLUSTER} \ #集群中的所有节点
     --initial-cluster-state ${CLUSTER_STATE} \ #初始集群状态
     --initial-cluster-token ${TOKEN} \ #集群的token
start_etcd2.sh
#!/bin/bash

TOKEN=token-cluster
CLUSTER_STATE=new
NAME_1=etcd_1
NAME_2=etcd_2
NAME_3=etcd_3
HOST_1=127.0.0.1
HOST_2=127.0.0.1
HOST_3=127.0.0.1
PORT_API_1=2379
PORT_PEER_1=2380
PORT_API_2=2479
PORT_PEER_2=2480
PORT_API_3=2579
PORT_PEER_3=2580

CLUSTER=${NAME_1}=http://${HOST_1}:${PORT_PEER_1},${NAME_2}=http://${HOST_2}:${PORT_PEER_2},${NAME_3}=http://${HOST_3}:${PORT_PEER_3}

# For every machine
THIS_NAME=${NAME_2}
THIS_IP=${HOST_2}
THIS_PORT_API=${PORT_API_2}
THIS_PORT_PEER=${PORT_PEER_2}

etcd --data-dir=/tmp/etcd_data_conf/${THIS_NAME}/data_dir \
	 --name ${THIS_NAME} \
     --initial-advertise-peer-urls http://${THIS_IP}:${THIS_PORT_PEER} \
     --listen-peer-urls http://${THIS_IP}:${THIS_PORT_PEER} \
     --advertise-client-urls http://${THIS_IP}:${THIS_PORT_API} \
     --listen-client-urls http://${THIS_IP}:${THIS_PORT_API} \
     --initial-cluster ${CLUSTER} \
     --initial-cluster-state ${CLUSTER_STATE} \
     --initial-cluster-token ${TOKEN} \
start_etcd3.sh
#!/bin/bash

TOKEN=token-cluster
CLUSTER_STATE=new
NAME_1=etcd_1
NAME_2=etcd_2
NAME_3=etcd_3
HOST_1=127.0.0.1
HOST_2=127.0.0.1
HOST_3=127.0.0.1
PORT_API_1=2379
PORT_PEER_1=2380
PORT_API_2=2479
PORT_PEER_2=2480
PORT_API_3=2579
PORT_PEER_3=2580

CLUSTER=${NAME_1}=http://${HOST_1}:${PORT_PEER_1},${NAME_2}=http://${HOST_2}:${PORT_PEER_2},${NAME_3}=http://${HOST_3}:${PORT_PEER_3}

# For every machine
THIS_NAME=${NAME_3}
THIS_IP=${HOST_3}
THIS_PORT_API=${PORT_API_3}
THIS_PORT_PEER=${PORT_PEER_3}

etcd --data-dir=/tmp/etcd_data_conf/${THIS_NAME}/data_dir \
	 --name ${THIS_NAME} \
     --initial-advertise-peer-urls http://${THIS_IP}:${THIS_PORT_PEER} \
     --listen-peer-urls http://${THIS_IP}:${THIS_PORT_PEER} \
     --advertise-client-urls http://${THIS_IP}:${THIS_PORT_API} \
     --listen-client-urls http://${THIS_IP}:${THIS_PORT_API} \
     --initial-cluster ${CLUSTER} \
     --initial-cluster-state ${CLUSTER_STATE} \
     --initial-cluster-token ${TOKEN} \

启动脚本

bash start_etcd1.sh
bash start_etcd2.sh
bash start_etcd3.sh

查看节点信息

etcdctl --write-out=table member list

查看集群状态

etcdctl --endpoints=127.0.0.1:2379,127.0.0.1:2479,127.0.0.1:2579 --write-out=table endpoint status

查看节点健康状况

etcdctl --endpoints=127.0.0.1:2379,127.0.0.1:2479,127.0.0.1:2579 --write-out=table endpoint health

Docker启动Etcd集群

安装镜像

docker pull bitnami/etcd

启动单节点

docker run  -p 2380:2380 -p 2379:2379 \
 -e ETCD_NAME=etcd1\     #节点名
 -e ALLOW_NONE_AUTHENTICATION=yes \  #允许不验证身份
 -e ETCD_ADVERTISE_CLIENT_URLS=http://127.0.0.1:2379 \ 
 -e ETCD_INITIAL_ADVERTISE_PEER_URLS=http://127.0.0.1:2380 \
 -e ETCD_INITIAL_CLUSTER=etcd1=http://127.0.0.1:2380 \
 --name etcd1 bitnami/etcd

将主机的etcd数据目录挂载到docker实例的方法

试过好几种在docker run 附加data-dir参数的方法来挂载主机etcd数据目录的方法,但都失败了,docker实例的数据目录一直是默认的/bitnami/etcd/data,最后在实例里的etcd-env.sh脚本里发现,data-dir环境变量不在可修改的变量列表里。但试出了一种方法,创建docker实例时将本机的etcd数据目录挂载到实例里,再用root身份进入到实例,把想要的本机目录覆盖到实例里的默认目录。

docker run -d  \
  -p 2379:2379 \
  -p 2380:2380 \
  -v /tmp/etcd_data_conf/etcd_1/data_dir:/etcd-data \   #将主机目录挂载到实例根目录下的etcd-data
  -e ALLOW_NONE_AUTHENTICATION=yes \
  --name etcd1 bitnami/etcd 
  
docker exec -it -u root container_id bash  #以root身份进入到实例
cp -r /etcd-data/member /bitnami/etcd/data #用挂载的主机目录下的member文件夹覆盖实例里/bitnami/etcd/data目录下的member
exit  #退出实例
docker restart container_id #重启实例,数据即生效

启动三个节点

etcd1
docker run -d -p 2380:2380 -p 2379:2379 \
 -e ETCD_NAME=etcd1\
 -e ALLOW_NONE_AUTHENTICATION=yes \
 --network host \
 -e ETCD_ADVERTISE_CLIENT_URLS=http://127.0.0.1:2379 \
 -e ETCD_LISTEN_CLIENT_URLS=http://127.0.0.1:2379 \
 -e ETCD_INITIAL_ADVERTISE_PEER_URLS=http://127.0.0.1:2380 \
 -e ETCD_LISTEN_PEER_URLS=http://127.0.0.1:2380 \
 -e ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster \
 -e ETCD_INITIAL_CLUSTER=etcd1=http://127.0.0.1:2380,etcd2=http://127.0.0.1:2480,etcd3=http://127.0.0.1:2580 \
 -e ETCD_INITIAL_CLUSTER_STATE=new \
 --name etcd1 bitnami/etcd
etcd2
docker run -d -p 2480:2380 -p 2479:2379 \
 -e ETCD_NAME=etcd2\
 -e ALLOW_NONE_AUTHENTICATION=yes \
 --network host \
 -e ETCD_ADVERTISE_CLIENT_URLS=http://127.0.0.1:2479 \
 -e ETCD_LISTEN_CLIENT_URLS=http://127.0.0.1:2479 \
 -e ETCD_INITIAL_ADVERTISE_PEER_URLS=http://127.0.0.1:2480 \
 -e ETCD_LISTEN_PEER_URLS=http://127.0.0.1:2480 \
 -e ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster \
 -e ETCD_INITIAL_CLUSTER=etcd1=http://127.0.0.1:2380,etcd2=http://127.0.0.1:2480,etcd3=http://127.0.0.1:2580 \
 -e ETCD_INITIAL_CLUSTER_STATE=new \
 --name etcd2 bitnami/etcd
etcd3
docker run -d -p 2580:2380 -p 2579:2379 \
 -e ETCD_NAME=etcd3\
 -e ALLOW_NONE_AUTHENTICATION=yes \
 --network host \
 -e ETCD_ADVERTISE_CLIENT_URLS=http://127.0.0.1:2579 \
 -e ETCD_LISTEN_CLIENT_URLS=http://127.0.0.1:2579 \
 -e ETCD_INITIAL_ADVERTISE_PEER_URLS=http://127.0.0.1:2580 \
 -e ETCD_LISTEN_PEER_URLS=http://127.0.0.1:2580 \
 -e ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster \
 -e ETCD_INITIAL_CLUSTER=etcd1=http://127.0.0.1:2380,etcd2=http://127.0.0.1:2480,etcd3=http://127.0.0.1:2580 \
 -e ETCD_INITIAL_CLUSTER_STATE=new \
 --name etcd3 bitnami/etcd

GO操作Etcd方法

安装官方sdk至GOPATH

go get go.etcd.io/etcd/clientv3

导入sdk

import clientv3 "go.etcd.io/etcd/client/v3"

创建连接config.new()

client, err := clientv3.New(clientv3.Config{
    Endpoints:   []string{"ip:port"},
    DialTimeout: 5 * time.Second,
})
if err != nil {
    log.Fatalln("连接etcd失败")
}
defer client.Close()

PUT

ctx, cancel := context.WithTimeout(context.Background(), time.Second)
_, err = client.Put(ctx, "key", "value")
cancel()
if err != nil {
    log.Printf("PUT key to etcd error : %v\n", err)
    return
}

GET

ctx, cancel = context.WithTimeout(context.Background(), time.Second)
resp, err := client.Get(ctx, "name")
cancel()
if err != nil {
    log.Printf("GET key-value from etcd error : %v\n", err)
    return
}

for _, ev := range resp.Kvs {
    log.Printf("%s : %s\n", ev.Key, ev.Value)
}

DELETE

//clientv3.WithPrevKV()用于将删除的KV返回到delResp
delResp, err := client.Delete(context.TODO(),"foo1",clientv3.WithPrevKV())

//若成功删除数量大于0且删除参数加上clientv3.WithPrevKV()
if len(delResp.PrevKvs) > 0 {
    for _, kvpair := range delResp.PrevKvs {
        fmt.Printf("delete key is: %s \n Value: %s \n", string(kvpair.Key), string(kvpair.Value))
    }
}

TXN:事务, etcd中事务是原子执行的,只支持if … then … else …这种表达

//创建事务
txn := kv.Txn(context.TODO())

//判断如果k1的值大于v1并且k1的版本号是2,则Put 键值k2和k3,否则Put键值k4和k5
kv.Txn(context.TODO()).If(
 clientv3.Compare(clientv3.Value(k1), ">", v1),
 clientv3.Compare(clientv3.Version(k1), "=", 2)
).Then(
 clientv3.OpPut(k2,v2), clentv3.OpPut(k3,v3)
).Else(
 clientv3.OpPut(k4,v4), clientv3.OpPut(k5,v5)
).Commit()

OP:操作

  • func OpDelete(key string, opts …OpOption) Op

  • func OpGet(key string, opts …OpOption) Op

  • func OpPut(key, val string, opts …OpOption) Op

  • func OpTxn(cmps []Cmp, thenOps []Op, elseOps []Op) Op

      ops := []clientv3.Op{
          clientv3.OpPut("key", "value",),
          clientv3.OpGet("key"),
          clientv3.OpPut("key", "value")}
      
      for _, op := range ops {
          if _, err := cli.Do(context.TODO(), op); err != nil {
              log.Fatal(err)
          }
      }

WATCH:监听某个值的变化

// respCh 是一个通道
respCh := client.Watch(context.Background(), "key")
// 若respCh为空,则会阻塞在这里
for watchResp := range respCh {
    for _, v := range watchResp.Events {
        log.Printf("type =  %s , Key = %s , Value = %s\n", v.Type, v.Kv.Key, v.Kv.Value) //type是对这个值所进行的操作类型
    }
}

LEASE:租约,即为一个key设置有效时间

// 创建一个20秒钟的租约
resp, err := client.Grant(context.TODO(), 20)
if err != nil {
    log.Printf("client.Grant error : %v\n", err)
    return
}

// 20秒钟之后,这个key就会被移除
_, err = client.Put(context.TODO(), "key", "value", clientv3.WithLease(resp.ID))
if err != nil {
    log.Printf("client.Put error : %v\n", err)
    return
}

KEEP-ALIVE:保活,即使租约永久存活(程序关闭后,使用租约存储的值依旧会消失)

resp, err := client.Grant(context.TODO(), 20)
   if err != nil {
      log.Printf("client.Grant error : %v\n", err)
      return
   }

_, err = client.Put(context.TODO(), "key", "value", clientv3.WithLease(resp.ID))
if err != nil {
    log.Printf("client.Put error : %v\n", err)
    return
}

// key ,将永久被保存
keepAliveChannel, err := client.KeepAlive(context.TODO(), resp.ID)
if err != nil {
    log.Fatal(err)
}

/*
租期客户端每秒钟向etcd服务器发送keepalive请求
而返回的keepalive响应由keepAliveChannel来接收
如果这里不用循环去接收通道的值,在通道满了之后会发出警告
通道大小为16
*/
go func(){
    for {
        ka := <-keepAliveChannel
        log.Println("ttl:", ka.TTL)
    }
}()

分布式锁:不同会话使用同一个锁,同一个时间只能有一个会话上锁

import "go.etcd.io/etcd/client/v3/concurrency"

// 创建第一个会话
session1, err := concurrency.NewSession(client)
if err != nil {
    log.Printf("concurrency.NewSession 1 error : %v\n", err)
    return
}
defer session1.Close()
// 第一个会话设置锁
myMu1 := concurrency.NewMutex(session1, "/lock")

// 创建第二个会话
session2, err := concurrency.NewSession(client)
if err != nil {
    log.Printf("concurrency.NewSession 2 error : %v\n", err)
    return
}
defer session2.Close()
// 第二个会话设置锁
myMu2 := concurrency.NewMutex(session2, "/lock")

// 会话s1获取锁
if err := myMu1.Lock(context.TODO()); err != nil {
    log.Printf("myMu1.Lock error : %v\n", err)
    return
}

go func() {
    //只有当第一个会话解锁后,第二个会话才能上锁
    if err := myMu2.Lock(context.TODO()); err != nil {
        log.Printf("myMu2.Lock error : %v\n", err)
        return
    }
}()

// 第一个会话解锁
if err := myMu1.Unlock(context.TODO()); err != nil {
    log.Printf("myMu1.Unlock error : %v\n", err)
    return
}

Etcd可视化工具

etcd-manager

简介

这是一个免费的、跨平台的ETCD v3 客户端和 GUI。该项目的目标是双重的:

  • 为桌面(Windows、Linux、Mac)、移动设备(iOS 和 Android)和 Web 提供高效、现代的 GUI。

  • 涵盖所有 ETCD 功能。你可以用etcdctl做的任何事情,你也应该可以用这个工具做。这个应用程序应该对简单和高级用户都很有用。

注意,目前不支持ETCD V2 API ,仅支持V3。

支持的系统:macOS,windows,linux

官方下载链接

http://etcdmanager.io/

功能:

密钥管理:
  • 管理(浏览、创建、编辑、删除)密钥。

  • 使用 TTL 创建密钥

  • 关键浏览器有多个视图:树或带分页的列表。

  • 键列表实时更新:当任何键的值发生变化时刷新列表。

  • 管理修订:列出任何键的修订并恢复到任何以前的值。

设置和配置:
  • 能够使用多个配置文件,允许您使用专用设置管理任意数量的 ETCD 集群。

  • 导入/导出设置:将设置保存到文件或从文件加载。

身份验证和安全:
  • 基本认证:(用户名/密码)

  • HTTPS 客户端证书认证

  • 支持 HTTP 和 HTTPS(安全)连接

  • 支持无身份验证(禁用身份验证的 ETCD)

其他功能:
  • 显示ETCD集群及其节点的基本信息,进行健康检查。

  • 管理租约:列出和撤销租约,查看详细信息。

  • 管理用户:创建、更新或删除用户。

  • 管理角色和权限:创建、更新删除角色、分配/撤销权限。

  • 管理观察者。支持的事件响应程序:应用程序或桌面通知、应用程序控制台记录器。

  • 内联网模式:无需互联网连接即可工作。

说点什么吧...