kubernetes dashboard向外网提供服务

目前新版本的 kubernetes dashboard (https://github.com/kubernetes/dashboard)安装了后,为了安全起见,默认情况下已经不向外提供服务,只能通过 http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/ 本机访问。在我们学习过程中,总有些不方便,这时我们可以利用 kubectl proxy 命令来实现。

首先我们看一下此命令的一些想着参数

➜  ~ kubectl proxy -h
To proxy all of the kubernetes api and nothing else, use:

  $ kubectl proxy --api-prefix=/

To proxy only part of the kubernetes api and also some static files:

  $ kubectl proxy --www=/my/files --www-prefix=/static/ --api-prefix=/api/

The above lets you 'curl localhost:8001/api/v1/pods'.

To proxy the entire kubernetes api at a different root, use:

  $ kubectl proxy --api-prefix=/custom/

The above lets you 'curl localhost:8001/custom/api/v1/pods'

Examples:
  # Run a proxy to kubernetes apiserver on port 8011, serving static content from ./local/www/
  kubectl proxy --port=8011 --www=./local/www/

  # Run a proxy to kubernetes apiserver on an arbitrary local port.
  # The chosen port for the server will be output to stdout.
  kubectl proxy --port=0

  # Run a proxy to kubernetes apiserver, changing the api prefix to k8s-api
  # This makes e.g. the pods api available at localhost:8011/k8s-api/v1/pods/
  kubectl proxy --api-prefix=/k8s-api

Options:
      --accept-hosts='^localhost$,^127\.0\.0\.1$,^\[::1\]$': Regular expression for hosts that the proxy should accept.
      --accept-paths='^/.*': Regular expression for paths that the proxy should accept.
      --address='127.0.0.1': The IP address on which to serve on.
      --api-prefix='/': Prefix to serve the proxied API under.
      --disable-filter=false: If true, disable request filtering in the proxy. This is dangerous, and can leave you
vulnerable to XSRF attacks, when used with an accessible port.
  -p, --port=8001: The port on which to run the proxy. Set to 0 to pick a random port.
      --reject-methods='POST,PUT,PATCH': Regular expression for HTTP methods that the proxy should reject.
      --reject-paths='^/api/.*/pods/.*/exec,^/api/.*/pods/.*/attach': Regular expression for paths that the proxy should
reject.
  -u, --unix-socket='': Unix socket on which to run the proxy.
  -w, --www='': Also serve static files from the given directory under the specified prefix.
  -P, --www-prefix='/static/': Prefix to serve static files under, if static file directory is specified.

Usage:
  kubectl proxy [--port=PORT] [--www=static-dir] [--www-prefix=prefix] [--api-prefix=prefix] [options]

Use "kubectl options" for a list of global command-line options (applies to all commands).

这里我们只要关注其中的三个参数就可以了

--accept-hosts='^localhost$,^127\.0\.0\.1$,^\[::1\]$': Regular expression for hosts that the proxy should accept.
--address='127.0.0.1': The IP address on which to serve on.
--port=8001: The port on which to run the proxy. Set to 0 to pick a random port.

–accept-hosts 表示哪些客户端访问,默认只允许 localhost 和 127.0.0.1
–address 表示本机绑定的ip地址,如果值为0.0.0.0 则表示不限,通过任何ip都可以访问.
a
–port 表示代理的接口,如果值为0的话,则随机一个端口

这里为了外网访问,可设置如下

nohup kubectl proxy --address='0.0.0.0' --port=8888 --accept-hosts='^*$'

这时就实现了通过外网访问

http://192.168.0.107:8888/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/

其实说白了,只要你把基本的命令参数搞清楚了,实现起来就方便了,就看你基础牢不牢。

RabbitMQ常见面试题

1. RabbitMq的消息类型(6种)消息确认机制

2. RabbitMq中的概念及解释

  •  Server(Broker):接收客户端连接,实现AMQP协议的消息队列和路由功能的进程;
  •  Virtual Host:虚拟主机的概念,类似权限控制组,一个Virtual Host里可以有多个Exchange和Queue。   
  •  Exchange:交换机,接收生产者发送的消息,并根据Routing Key将消息路由到服务器中的队列Queue。
  •  ExchangeType:交换机类型决定了路由消息行为,RabbitMQ中有三种类型Exchange,分别是fanout、direct、topic;
  •  Message Queue:消息队列,用于存储还未被消费者消费的消息;
  •  Message:由Header和body组成,Header是由生产者添加的各种属性的集合,包括Message是否被持久化、优先级是多少、由哪个Message Queue接收等;body是真正需要发送的数据内容;
  • BindingKey:绑定关键字,将一个特定的Exchange和一个特定的Queue绑定起来。

2. 对mq有哪些理解(rabbitmq的模型,rabbitmq的特性,以及幂等问题),面试官问幂等如何解决(回答把token存在redis里头,然后检测是否存在token),面试官追问,难道不会有并发存token的情况吗(回答redis是单线程的,会有顺序,set同一个值成功会返回操作的行数)。

3.rabbitmq交换机有哪几种模式,他是如何来保证数据不丢失的,持久化机制,消息确认机制等等

4.消息队列的应用场景,rabbitmq是推模式还是拉模式

5.RabbitMQ的exchange有几种?RabbitMQ的queue有几种

6.rabbitmq队列可以连多少个消费者。

7.rabbitmq 交换机 持久化 确认机制 消息丢失处理

8.RabbitMQ如何保证可靠性:持久化+确认机制,持久化:消息、Exchange、Queue都会持久化,确认机制,比如消息投递上去后,RabbitMQ会在存到硬盘后返回ACK,根据ACK可以判断是否投递成功,以上保证了消息的可靠。

9. 如何让你设计一个消息队列,如何设计?

https://www.jianshu.com/p/67c14aebd5b2

rabbitmq消息队列的消息持久化机制

首先阅读这篇文章:https://blog.csdn.net/yongche_shi/article/details/51500534

之前其实已经写过一篇关于RabbitMQ持久化的 文章 ,但那篇文章侧重代码层面的写入流程,对于持久化操作何时发生以及什么时候会刷新到磁盘等问题其实都没有搞清楚,这篇文章着重于关注这些问题。

消息什么时候需要持久化?

根据 官方博文 的介绍,RabbitMQ在两种情况下会将消息写入磁盘:

  1. 消息本身在publish的时候就要求消息写入磁盘;
  2. 内存紧张,需要将部分内存中的消息转移到磁盘;

消息什么时候会刷到磁盘?

  1. 写入文件前会有一个Buffer,大小为1M(1048576),数据在写入文件时,首先会写入到这个Buffer,如果Buffer已满,则会将Buffer写入到文件(未必刷到磁盘);
  2. 有个固定的刷盘时间:25ms,也就是不管Buffer满不满,每隔25ms,Buffer里的数据及未刷新到磁盘的文件内容必定会刷到磁盘;
  3. 每次消息写入后,如果没有后续写入请求,则会直接将已写入的消息刷到磁盘:使用Erlang的receive x after 0来实现,只要进程的信箱里没有消息,则产生一个timeout消息,而timeout会触发刷盘操作。

消息在磁盘文件中的格式

消息保存于$MNESIA/msg_store_persistent/x.rdq文件中,其中x为数字编号,从1开始,每个文件最大为16M(16777216),超过这个大小会生成新的文件,文件编号加1。消息以以下格式存在于文件中:

<<Size:64, MsgId:16/binary, MsgBody>>

MsgId为RabbitMQ通过rabbit_guid:gen()每一个消息生成的GUID,MsgBody会包含消息对应的exchange,routing_keys,消息的内容,消息对应的协议版本,消息内容格式(二进制还是其它)等等。

文件何时删除(垃圾回收)?

PUBLISH消息时写入内容,ack消息时删除内容(更新该文件的有用数据大小),当一个文件的有用数据等于0时,删除该文件。

由于执行消息删除操作时,并不立即对在文件中对消息进行删除,也就是说消息依然在文件中,仅仅是垃圾数据而已。当垃圾数据(已经被删除的消息)比例超过一定阈值后(默认比例GARBAGE_FRACTION = 0.5即50%),并且至少有三个及以上的文件时,rabbitmq触发垃圾回收文件合并操作,以提高磁盘利用率。垃圾回收会先找到符合要求的两个文件(根据#file_summary{}中left,right找逻辑上相邻的两个文件,并且两个文件的有效数据可在一个文件中存储),然后锁定这两个文件,并先对左边文件的有效数据进行整理,再将右边文件的有效数据写入到左边文件,同时更新消息的相关信息(存储的文件,文件中的偏移量),文件的相关信息(文件的有效数据,左边文件,右边文件),最后将右边的文件删除。

https://blog.csdn.net/yongche_shi/article/details/51500623

docker exec 命令原理

我们经常使用 docker exec 命令进入到一个容器里进行一些操作,那么这个命令是如果进入到容器里的呢?想必大家都知道用到了Namespace来实现,但至于底层实现原理是什么,想必都不是特别清楚吧。

我们知道容器的本质其实就是一个进程,每个进程都有一个Pid,至于容器的Pid值可以通过 docker inspect container_id 来查看,我们这里是一个Python应用容器,我们看一下他的 Pid值

docker inspect --format '{{ .State.Pid }}'  4ddf4638572d
25686

而每个进程都有自己的Namespace,你可以通过查看宿主机的 proc 文件,看到这个 25686 进程的所有 Namespace 对应的文件

ls -l  /proc/25686/ns
total 0
lrwxrwxrwx 1 root root 0 Aug 13 14:05 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Aug 13 14:05 ipc -> ipc:[4026532278]
lrwxrwxrwx 1 root root 0 Aug 13 14:05 mnt -> mnt:[4026532276]
lrwxrwxrwx 1 root root 0 Aug 13 14:05 net -> net:[4026532281]
lrwxrwxrwx 1 root root 0 Aug 13 14:05 pid -> pid:[4026532279]
lrwxrwxrwx 1 root root 0 Aug 13 14:05 pid_for_children -> pid:[4026532279]
lrwxrwxrwx 1 root root 0 Aug 13 14:05 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Aug 13 14:05 uts -> uts:[4026532277]

可以看到,一个进程的每种 Linux Namespace,都在它对应的 /proc/[进程号]/ns 下有一个对应的虚拟文件,并且链接到一个真实的 Namespace 文件上。可以看到这个容器对应的Net Namespace Id为 4026532281

进入容器的命令为

$ docker exec -it 4ddf4638572d /bin/sh

对于我们执行的 /bin/sh 命令,进程PID在宿主机器上是可以看到的。

在宿主机上

 $ ps aux | grep /bin/bash
 root     28499  0.0  0.0 19944  3612 pts/0    S    14:15   0:00 /bin/bash

实际上,Linux Namespace 创建的隔离空间虽然看不见摸不着,但一个进程的 Namespace 信息在宿主机上是确确实实存在的,并且是以一个文件的方式存在。

我们现在看一下这两个进程对应的Namespace信息

ls -l /proc/28499/ns/net
lrwxrwxrwx 1 root root 0 Aug 13 14:18 /proc/28499/ns/net -> net:[4026532281]

$ ls -l  /proc/25686/ns/net
lrwxrwxrwx 1 root root 0 Aug 13 14:05 /proc/25686/ns/net -> net:[4026532281]

/proc/[PID]/ns/net 目录下,这个 PID=28499 进程,与我们前面的 Docker 容器进程(PID=25686)指向的 Network Namespace 文件完全一样。这说明这两个进程,共享了这个名叫 net:[4026532281] 的 Network Namespace。

此外,Docker 还专门提供了一个参数,可以让你启动一个容器并“加入”到另一个容器的 Network Namespace 里,这个参数就是 -net,比如:

docker run -it --net container:4ddf4638572d busybox ifconfig

这样,我们新启动的这个容器,就会直接加入到 ID=4ddf4638572d 的容器,也就是我们前面的创建的 Python 应用容器(PID=25686)的 Network Namespace 中。参考文章:docker容器调试利器nicolaka/netshoot

而如果我指定–net=host,就意味着这个容器不会为进程启用 Network Namespace。这就意味着,这个容器拆除了 Network Namespace 的“隔离墙”,所以,它会和宿主机上的其他普通进程一样,直接共享宿主机的网络栈。这就为容器直接操作和使用宿主机网络提供了一个渠道。

现在我们知道了 docker exec 的原理,那么对于 docker run -v /home:/test … 是如何实际目录挂载的呢?

建议参考:https://time.geekbang.org/column/article/18119

docker中的命名空间

Namespace 的作用是“隔离”,它让应用进程只能看到该Namespace 内的“世界”;而 Cgroups 的作用是“限制”,它给这个“世界”围上了一圈看不见的墙。

命名空间是 Linux 内核一个强大的特性。每个容器都有自己单独的命名空间,运行在其中的应用都像是在独立的操作系统中运行一样。命名空间保证了容器之间彼此互不影响。

在docker中一共有以下几个命名空间,每个Namespace的发挥着不同的作用。

pid 命名空间

不同用户的进程就是通过 pid 命名空间隔离开的,且不同命名空间中可以有相同 pid。在同一个Namespace中只能看到当前命名空间的进程。所有的 LXC 进程在 Docker 中的父进程为Docker进程,每个 LXC 进程具有不同的命名空间。同时由于允许嵌套,因此可以很方便的实现嵌套的 Docker 容器。

net 命名空间

有了 pid 命名空间, 每个命名空间中的 pid 能够相互隔离,但是网络端口还是共享 host 的端口。网络隔离是通过 net 命名空间实现的, 每个 net 命名空间有独立的 网络设备, IP 地址, 路由表, /proc/net 目录。这样每个容器的网络就能隔离开来。Docker 默认采用 veth 的方式,将容器中的虚拟网卡同 host 上的一 个Docker 网桥 docker0 连接在一起。

ipc 命名空间

容器中进程交互还是采用了 Linux 常见的进程间交互方法(interprocess communication – IPC), 包括信号量、消息队列和共享内存等。然而同 VM 不同的是,容器的进程间交互实际上还是 host 上具有相同 pid 命名空间中的进程间交互,因此需要在 IPC 资源申请时加入命名空间信息,每个 IPC 资源有一个唯一的 32 位 id。

mnt 命名空间

类似 chroot,将一个进程放到一个特定的目录执行。mnt 命名空间允许不同命名空间的进程看到的文件结构不同,这样每个命名空间 中的进程所看到的文件目录就被隔离开了。同 chroot 不同,每个命名空间中的容器在 /proc/mounts 的信息只包含所在命名空间的 mount point。

uts 命名空间

UTS(“UNIX Time-sharing System”) 命名空间允许每个容器拥有独立的 hostname 和 domain name, 使其在网络上可以被视作一个独立的节点而非 主机上的一个进程。

user 命名空间

每个容器可以有不同的用户和组 id, 也就是说可以在容器内用容器内部的用户执行程序而非主机上的用户。

*注:更多关于 Linux 上命名空间的信息,请阅读 这篇文章

docker容器调试利器nicolaka/netshoot

背景

在日常工作中,我们一般会将容器进行精简,将其大小压缩到最小,以此来提高容器部署效率,参考小米云技术 – Docker最佳实践:5个方法精简镜像。但有一个比较尴尬的问题就是对容器排障,由于容器里没有了我们日常工作中用到许多排障命令,如top、ps、netstat等,所以想排除故障的话,常用的做法是安装对应的命令,如果容器过多的话,再这样搞就有些麻烦了,特别是在一些安装包源速度很慢的情况。

解决方案

今天发现一篇文章(简化 Pod 故障诊断:kubectl-debug 介绍)介绍针对此类问题的解决方案的,这里介绍的是一个叫做 kubectl-debug 的命令,主要由国内知名的PingCAP公司出品的,主要是用在k8s环境中的。我们知道容器里主要两大技术,一个是用cgroup来实现容器资源的限制,一个是用Namespace来实现容器的资源隔离的)。(kubectl-debug 命令是基于一个工具包(https://github.com/nicolaka/netshoot) 来实现的,其原理是利用将一个工具包容器添加到目标容器所在的Pod里,实现和目标容器的Network Namespace一致,从而达到对新旧容器进程的相互可见性,这样我们就可以直接在目标容器里操作这些命令。所以在平时开发环境中,可以很方便的利用此原理直接使用这个工具包来实现对容器的排障。

在 Kubernetes 项目中,这些容器则会被划分为一个“Pod”,Pod 里的容器共享同一个 Network Namespace、同一组数据卷,从而达到高效率交换信息的目的。这些容器应用就可以通过 Localhost 通信,通过本地磁盘目录交换文件。

netshoot包含一组强大的工具,如图所示

工具包清单

apache2-utils
bash
bind-tools
bird
bridge-utils
busybox-extras
calicoctl
conntrack-tools
ctop
curl
dhcping
drill
ethtool
file
fping
iftop
iperf
iproute2
iptables
iptraf-ng
iputils
ipvsadm
libc6-compat
liboping
mtr
net-snmp-tools
netcat-openbsd
netgen
nftables
ngrep
nmap
nmap-nping
openssl
py-crypto
py2-virtualenv
python2
scapy
socat
strace
tcpdump
tcptraceroute
util-linux
vim

看了上图不得不说几乎包含了所有的调度命令。

使用也很方便,只需要一条命令即可

$ docker run -it --net container:<container_name> nicolaka/netshoot

参数 --net--network 的缩写,有四种值,用法参考:https://docs.docker.com/engine/reference/run/#network-settings

执行命令后,会自动创建一个镜像为 nicolaka/netshoot 的容器,

Docker 提供的这个 --net 参数,可以让你启动一个容器并“加入”到另一个容器的 Network Namespace 里,并共享一个网络栈,即Namespace 和目标容器的一样,这样就实现了合并命令工具到目标容器 里。如果执行命令 hostname 命令的话,结果显示的是目标 “容器ID” 。此时就可以在容器里执行常用的一些ps、 top、netstat、iftop 之类的命令。

➜  ~ docker run -it --rm --network container:mysql80 nicolaka/netshoot
                    dP            dP                           dP
                    88            88                           88
88d888b. .d8888b. d8888P .d8888b. 88d888b. .d8888b. .d8888b. d8888P
88'  `88 88ooood8   88   Y8ooooo. 88'  `88 88'  `88 88'  `88   88
88    88 88.  ...   88         88 88    88 88.  .88 88.  .88   88
dP    dP `88888P'   dP   `88888P' dP    dP `88888P' `88888P'   dP

Welcome to Netshoot! (github.com/nicolaka/netshoot)
root @ /
 [1] 🐳  → ls
bin    dev    etc    home   lib    lib64  media  mnt    opt    proc   root   run    sbin   srv    sys    tmp    usr    var

root @ /
 [2] 🐳  → hostname
7ff422d3f75d

root @ /
 [3] 🐳  → ps
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/bash -l
   15 root      0:00 ps

root @ /
 [4] 🐳  → netstat -an | grep LISTEN
tcp        0      0 :::33060                :::*                    LISTEN
tcp        0      0 :::3306                 :::*                    LISTEN
unix  2      [ ACC ]     STREAM     LISTENING      18007 /var/run/mysqld/mysqld.sock
unix  2      [ ACC ]     STREAM     LISTENING      19694 /var/run/mysqld/mysqlx.sock

root @ /
 [5] 🐳  →

上面我们添加了--rm 参数,主要是为了使用完毕后,及时清除临时容器相关的资源。这里我们将临时容器的Namespace和mysql80 容器相同

mysql80容器的ID为 7ff422d3f75d,即hostname 命令的输出结果。

现在我们先不要从这个容器里退出,再开启一个新终端进入到临时容器(bb47226c955e)里

➜  ~ docker exec -it bb4 /bin/bash
bash-5.0# hostname
7ff422d3f75d
bash-5.0# ps
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/bash -l
   22 root      0:00 /bin/bash
   28 root      0:00 ps
bash-5.0# netstat -an | grep LISTEN
tcp        0      0 :::33060                :::*                    LISTEN
tcp        0      0 :::3306                 :::*                    LISTEN
unix  2      [ ACC ]     STREAM     LISTENING      18007 /var/run/mysqld/mysqld.sock
unix  2      [ ACC ]     STREAM     LISTENING      19694 /var/run/mysqld/mysqlx.sock
bash-5.0#

从hostname和进程ID的结果来看,进入的还是msyql80容器。

最后执行 exit 或者 logout 命令退出容器,此时临时容器及其volume将自动被删除(参数–rm)。

总结

这里主要考察了容器的Namespace隔离性的原理,通过将一个容器所有进程加入到目标容器的Namespace,从而实现了两个容器进程的相互可见。

问题延伸

这里抛出另一个问题,如果两个容器都存在一个一模一样的进程,这个时候会出错吗?如果不会的话,为什么(应用存储路径不一样?或者进程PID不一样)?我们可见的是哪个容器的里程,临时容器还是目标容器呢?

把一个进程分配到一个指定的Namespace下的原理请参考:https://time.geekbang.org/column/article/18119

基于docker环境实现Elasticsearch 集群环境

最近搭建了es集群的时候,现在需要测试添加一个新的数据节点,项目是使用docker-compose命令来搭建的。

以下基于最新版本 es7.2.0进行, 配置文件目录为 es, 所以docker 在创建网络的时候,网络名称会以 es_ 前缀开始,如本例中我们在docker-composer.yaml文件中指定了网络名称为esnet,但docker生成的实例名称为 es_esnet,至于网络相关的信息可以通过 docker network --help 查看。

搭建es集群

// docker-compose.yaml 集群配置文件

version: '2.2'
services:
  es01:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.2.0
    container_name: es01
    environment:
      - node.name=es01
      - node.master=true
      - node.data=true
      - discovery.seed_hosts=es02
      - cluster.initial_master_nodes=es01,es02
      - cluster.name=docker-cluster
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - esdata01:/usr/share/elasticsearch/data
    ports:
      - 9200:9200
    networks:
      - esnet
  es02:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.2.0
    container_name: es02
    environment:
      - node.name=es02
      - discovery.seed_hosts=es01
      - cluster.initial_master_nodes=es01,es02
      - cluster.name=docker-cluster
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - esdata02:/usr/share/elasticsearch/data
    networks:
      - esnet
  es03:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.2.0
    container_name: es03
    environment:
      - node.name=es03
      - discovery.seed_hosts=es01
      - cluster.initial_master_nodes=es01,es02
      - cluster.name=docker-cluster
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - esdata03:/usr/share/elasticsearch/data
    networks:
      - esnet
volumes:
  esdata01:
    driver: local
  esdata02:
    driver: local
  esdata03:
    driver: local

networks:
  esnet:

集群配置了3个master节点,并同时作为数据节点使用,当节点未指定 node.master和node.data的时候,默认值为 true 。执行命令

$ docker-compose up

启动集群。

验证集群环境是否搭建成功,在浏览器里访问 http://localhost:9200 http://localhost:9200/_cat/nodes?v 显示正常。三个节点角色均为mdi

ip           heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
192.168.16.3           28          74   3    0.14    0.94     4.97 mdi       *      es02
192.168.16.4           28          74   3    0.14    0.94     4.97 mdi       -      es01
192.168.16.2           25          74   3    0.14    0.94     4.97 mdi       -      es03

添加集群新的节点

添加es数据节点文件 join-docker-compose.yaml

version: '2.2'
services:
  es04:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.2.0
    container_name: es04
    environment:
      - node.name=es04
      - node.master=false
      - node.data=true
      - cluster.initial_master_nodes=es01,es02
      - cluster.name=docker-cluster
      - discovery.seed_hosts=es01
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - esdata04:/usr/share/elasticsearch/data
    networks:
      - esnet

  
volumes:
  esdata04:
    driver: local

networks:
  esnet:
    external:
      name: es_esnet

这里指定了 node.master=false,执行命令

$ docker-compose -f join-docker-compose.yaml up

注意这里手动指定了 yaml 文件,两个配置文件都在同一个es目录里。

再次使用上面的 http://localhost:9200/_cat/nodes?v 进行验证

ip           heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
192.168.16.3           26          93  19    0.44    0.28     1.30 mdi       *      es02
192.168.16.5           22          93  14    0.44    0.28     1.30 di        -      es04
192.168.16.4           15          93  18    0.44    0.28     1.30 mdi       -      es01
192.168.16.2           37          93  20    0.44    0.28     1.30 mdi       -      es03

这里我们可以看到新增加的节点 es04,节点角色为di, 这个节点由于指定了 node.master=false 说明此节点并不参与master leader节点的选举。

集群节点使用的docker网络为 es_esnet

测试es集群master的选举(高可用)

上面我们可以看到当前es02这个master节点为leader,我们现在手动停止这个master容器,让其它的两个master中选举一个leader,执行命令

$ docker stop es02

此时,再用上面的方法查看一下集群节点情况

ip           heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
192.168.16.5           40          74   2    0.19    0.27     0.94 di        -      es04
192.168.16.4           15          74   2    0.19    0.27     0.94 mdi       *      es01
192.168.16.2           39          74   2    0.19    0.27     0.94 mdi       -      es03

可以看到es02节点消失了,现在的master leader 节点为 es01。如果我们再把容器启动起来的话,发现es02作为了一个普通的master节点加入到了集群。

注意事项:

其实es集群的环境搭建挺容易的,我在搭建过程中遇到了一此坑,花费了好久才算爬出来,下面记录下来供大家参考。

一、确认分配给 docker 软件的内存是否足够

在上一篇文章( https://blog.haohtml.com/archives/18981) 里已经写过了,由于docker 分配的内存不足,导致启动一个新的es节点,会直接killed掉原来的es节点,发生OOM的现象,并且这个问题通过直接查看容器日志根本排查不到,容器内并未有相关的日志信息

二、保证环境的干净

使用 docker-compose 命令启动es节点容器成功后,如果需要对旧节点的配置内容进行修改的话,则强烈建议执行以下命令进行容器的清理工作

$ docker-compose -f 配置文件.yaml down -v

以上命令会将容器及容器关联的数据卷 volume 信息进行一并删除。否则容易出现新启动的节点又单独变成了一个集群,这时会出现跨集群节点加入被拒绝的错误。我在搭建环境的时候,创建用的 docker-compose up 命令,但修改配置文件后,手动执行 “docker rm 容器ID” 将容器删除,再次执行了 docker-compose up命令时,会出现上面说的这个问题,在这个坑里呆了好久才算出来。如果一定要想用docker rm 命令删除容器的话,添加添加-f参数,将volume 一并删除,如 docker rm -f es03

三、参数 discovery.zen.minimum_master_nodes

这里用的是es7.2.0的版本,服务启动时提示参数项discovery.zen.minimum_master_nodes 在下一个版本中即将废除的,但在官方文档里没有找到说明信息,这一点待确认。

golang中的内存对齐(进阶必看)

先看一个结构体

// 写法一
type T1 struct {
	a int8
	b int64
	c int16
}

// 写法二
type T2 struct {
	a int8
	c int16
	b int64
}

对于这两个结构体,都有a、b、c三个定义完全一样的字段,只是在定义结构体的时候字段顺序不一样而已,那么两种写法有什么影响吗?

对于新手来说,感觉着没有什么区别的,只是一个书写顺序不同而已,但对于go编译器来说,则有着很大的区别,特别是在不同架构上(32位/64位)的编译器,在一定程度上对内存的使用大小和执行效率有着一定的不同。这里的主要知识点就是golang语言中的内存对齐概念(alignment guarantee),https://gfw.go101.org/article/memory-layout.html

类型的尺寸和结构体字节填充(structure padding)

Go白皮书只对以下种类的类型的尺寸进行了明确规定

类型种类                  尺寸(字节数)
------                   ------
byte, uint8, int8        1
uint16, int16            2
uint32, int32, float32   4
uint64, int64            8
float64, complex64       8
complex128               16
uint, int                取决于编译器实现。通常在
                         32位架构上为4,在64位
                         架构上为8。
uintptr                  取决于编译器实现。但必须
                         能够存下任一个内存地址。

Go白皮书没有对其它种类的类型的尺寸最初明确规定。 请阅读值复制成本一文来获取标准编译器使用的各种其它类型的尺寸。

标准编译器(和gccgo编译器)将确保一个类型的尺寸为此类型的对齐保证的倍数。

为了满足上一节中规定的地址对齐保证要求,Go编译器可能会在结构体的相邻字段之间填充一些字节。 这使得一个结构体类型的尺寸并非等于它的各个字段类型尺寸的简单相加之和。

下面是一个展示了一些字节是如何填充到一个结构体中的例子。 首先,从上面的描述中,我们已得知(对于标准编译器来说):

  • 内置类型int8的对齐保证和尺寸均为1个字节; 内置类型int16的对齐保证和尺寸均为2个字节; 内置类型int64的尺寸为8个字节,但它的对齐保证在32位架构上为4个字节,在64位架构上为8个字节。
  • 下例中的类型T1T2的对齐保证均为它们的各个字段的最大对齐保证。 所以它们的对齐保证和内置类型int64相同,即在32位架构上为4个字节,在64位架构上为8个字节。
  • 类型T1T2尺寸需为它们的对齐保证的倍数,即在32位架构上为4n个字节,在64位架构上为8n个字节。
type T1 struct {
	a int8

	// 在64位架构上,为了让下一个字段b的地址为8字节对齐,
	// 需在在字段a这里填充7个字节。在32位架构上,为了让
	// 字段b的地址为4字节对齐,需在这里填充3个字节。

	b int64
	c int16

	// 为了让类型T1的尺寸为T1的对齐保证的倍数,
	// 在64位架构上需在这里填充6个字节,在32架构
	// 上需在这里填充2个字节。
}
// 类型T1的尺寸在64位架构上位24个字节(1+7+8+2+6),
// 在32位架构上为16个字节(1+3+8+2+2)。
// 以保存每个字段都是8(64位架构)或者4(32位架构)的的整数倍

type T2 struct {
	a int8

	// 为了让下一个字段c的地址为2字节对齐,
	// 需在字段a这里填充1个字节。

	c int16

	// 在64位架构上,为了让下一个字段b的地址为8字节对齐,
	// 需在字段c这里填充4个字节。在32位架构上,不需填充
	// 字节即可保证字段b的地址为4字节对齐的。

	b int64
}
// 类型T2的尺寸在64位架构上位16个字节(1+1+2+4+8),
// 在32位架构上为12个字节(1+1+2+8)。

从这个例子可以看出,尽管类型T1T2拥有相同的字段集,但是它们的尺寸并不相等。每个字段的大小都要受下一个字段大小的影响,以方便下个字段对齐。所以建议在开发中,字段占用空间小的放在前面。

T1的内存对齐
T2的内存对齐

一个有趣的事实是有时候一个结构体类型中零尺寸类型的字段可能会影响到此结构体类型的尺寸。 请阅读此问答获取详情。

这里推荐一个网站:http://golang-sizeof.tips/,可以用来查看结构的内存布局,Type size的值越小越好,这样就可以对结构体进行一些内存大小优化了。

如果还有些模糊的话可以看一下这篇文章:https://blog.csdn.net/Lazyboy_/article/details/88579966

kafka常用术语

官方网站:http://kafka.apache.org/,中文: http://kafka.apachecn.org/

注意它和其它消息系统(消息队列)在定义上的区别,以便更好的理解它的应用场景。Apache Kafka 是一款分布式流处理平台(Distributed Streaming Platform)

术语(注意加粗部分的定义):

消息:Record。消息实体,是通信的基本单位。
主题:Topic。主题是承载消息的逻辑容器,在实际使用中多用来区分具体的业务。
分区:Partition。一个有序不变的消息序列。每个主题Topic下可以有多个分区。
消息位移:Offset。表示分区中每条消息的位置信息,是一个单调递增且不变的值。
缓存代理,Broker。Kafka集群中的一台或多台服务器统称broker。
副本:Replica。Kafka 中同一条消息能够被拷贝到多个地方以提供数据冗余,这些地方就是所谓的副本。副本还分为领导者副本和追随者副本,各自有不同的角色划分。副本是在分区层级下的,即每个分区可配置多个副本实现高可用。
生产者:Producer。向主题发布新消息的应用程序。
消费者:Consumer。从主题订阅新消息的应用程序。
消费者位移:Consumer Offset。表示消费者消费进度,每个消费者都有自己的消费者位移。
消费者组:Consumer Group。多个消费者实例共同组成的一个组,同时消费多个分区以实现高吞吐。
重平衡:Rebalance。消费者组内某个消费者实例挂掉后,其他消费者实例自动重新分配订阅主题分区的过程。Rebalance 是 Kafka 消费者端实现高可用的重要手段。

kafka

转自极客时间

想一下,为什么kafka比其它消息系统有着很高的吞吐量??提示 “零拷贝“https://www.cnblogs.com/f-ck-need-u/p/7615914.html

ES集群的高可用性之节点

为了防止ES集群中单点问题,一般都需要对集群节点做高可用性,当发生单点问题时,也可以向外正常提供服务。这里主要记录一下节点的加入、离开和主节点选举。

集群安装教程请参考:https://www.elastic.co/guide/en/elasticsearch/reference/current/install-elasticsearch.html

集群是由多个节点组成的,每个节点都扮演着不同的角色,一般常用的有 Master、Data 和 client。节点角色介绍:https://www.jianshu.com/p/7c4818dda91a

节点角色配置参数:
node.master: true
node.data: false
node.ingest: false 

在一个集群中,可以通过 http://localhost:9200/_cat/nodes?v 查看到每个节点在集群中扮演的角色,一个节点可同时拥有多个角色,如值MDI,同时也是每个节点的默认值,其中的 Ingest 节点也称作预处理节点,不过在生产环境中一般将master 和 data节点分开的。所有节点默认都是支持 Ingest 操作的。节点组合参考:https://blog.51cto.com/michaelkang/2061712

新节点的加入

随着数量大的增加,有时候我们不得进行机器的扩容,这时间就需要加入一些新的机器节点,用来提高访问速度。

当一个新节点加入的时候,它通过读取 discovery.zen.ping.unicast.hosts 配置的节点获取集群状态,然后找到 master 节点,并向其发送一个join request(discovery.zen.join_timeout)。主节点接收到请求后,同步集群状态到新节点。

节点离开

这里指非 mastr 节点。
当master主节点定期ping(ping_interval 默认为1s)一个节点出现3次ping不通的情况时(ping_timeout 默认为30s),主节点会认为该节点已宕机,将该节点踢出集群。

主节点的选举

我们知道集群中master节点角色的重要性,如果此节点出现问题的话,服务基本处于不可用状态了,所以保证master节点的高可用性至关重要。

当主节点发生故障时,集群中的其他节点将会ping当前的 master eligible 节点,并从中选出一个新的主节点。
节点可以通过设置 node.master=true 来设置自己的角色为主节点。
通过配置discovery.zen.minimum_master_nodes防止集群出现脑裂。该配置通过检查集群中 master eligible 的个数来判断是否选举出一个主节点。其个数最好设置为(number_master eligible/2)+1,防止当主节点出问题时,一个集群分裂为两个集群。
具有最小编号的active master eligible node将会被选举为master节点。参考:https://www.elastic.co/guide/cn/elasticsearch/guide/current/important-configuration-changes.html

脑裂的概念:
如果你有2个Master候选节点,并设置最小Master节点数为1,当网络抖动或偶然断开时,2个Master都会认为另一个Master挂掉了,他们都被选举为主Master,则此时集群中存在两个主Master,即物理上1个集群变成了逻辑上的2个集群,而当其中一个Master再次挂掉时,即便它恢复后回到了原有的集群,在它作为主Master期间写入的数据都会丢失,因为它上面维护了Index信息。类似我们平时的投票选举一下,10个人如果赞成与返对的比例为5:5就不太好了,所以必须保证双方的票数无法完全一样才可以。

为了防止脑裂,常常设置参数为discovery.zen.minimum_master_nodes=N/2+1,其中N为集群中Master节点的个数。建议集群中Master节点的个数为奇数个,如3个或者5个。

在版本 6 和更早的版本中,还有一些其他以 discovery.zen.* 开头的选项,允许你配置 Zen Discovery 的行为。其中一些设置不再有效,已被删除。其他的已经改名。如果一个参数已经被改名,那么它的旧名称在版本 7 中就被弃用,你需要调整配置来使用新名称。

新的集群协调子系统包括一个新的故障检测机制。这意味着 discovery.zen.fd.* 开头的 Zen Discovery 错误检测设置不再有效。大多数用户应该在版本 7 或更高版本中使用默认的故障检测配置,如果需要进行任何更改,可以使用cluster.fault_detection.*

elasticsearch.yml 配置详解:https://www.cnblogs.com/zlslch/p/6419948.html

参考文档:
https://www.jianshu.com/p/80b030a5b500
https://www.jianshu.com/p/7c4818dda91a

ElasticSearch最全详细使用教程:入门、索引管理、映射详解