404频道

学习笔记

经常有快速创建一个测试k8s集群的场景,为了能够快速完成,整理了如下的命令,即可在主机上快速启动一个k8s集群。部分命令需要外网访问,推荐直接使用海外的主机。

常用工具安装

1
2
3
4
5
6
7
8
9
yum install vim git make -y

# neovim
curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim-linux64.tar.gz
sudo rm -rf /opt/nvim
sudo tar -C /opt -xzf nvim-linux64.tar.gz
echo 'export PATH="$PATH:/opt/nvim-linux64/bin"' >>~/.bash_profile
source ~/.bash_profile
git clone https://github.com/LazyVim/starter ~/.config/nvim

安装docker

下面命令可以安装最新版本的docker-ce

1
2
3
4
5
6
7
8
9
10
11
12
13
14
yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
yum install -y yum-utils
yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
yum install docker-ce docker-ce-cli containerd.io -y
systemctl enable docker && systemctl start docker

安装特定版本的docker

如果要安装特定版本的docker-ce,可以使用如下方法。

使用如下命令查询yum源中的docker-ce版本

1
2
3
4
5
6
yum list docker-ce --showduplicates | sort -r
Last metadata expiration check: 0:00:27 ago on Sat 09 Apr 2022 12:39:09 AM CST.
docker-ce.x86_64 3:20.10.9-3.el8 docker-ce-stable
docker-ce.x86_64 3:20.10.8-3.el8 docker-ce-stable
docker-ce.x86_64 3:20.10.7-3.el8 docker-ce-stable
docker-ce.x86_64 3:20.10.6-3.el8 docker-ce-stable

选择特定版本的docker-ce和docker-ce-cli,执行如下命令

1
yum install docker-ce-<VERSION_STRING> docker-ce-cli-<VERSION_STRING> containerd.io

安装kubectl kind helm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 安装kubectl
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
mv kubectl /usr/bin/
# 如果找不到rpm包,可以通过网络安全 rpm -ivh http://mirror.centos.org/centos/7/os/x86_64/Packages/bash-completion-2.1-8.el7.noarch.rpm
yum install -y bash-completion
echo -e '\n# kubectl' >> ~/.bash_profile
echo 'source <(kubectl completion bash)' >>~/.bash_profile
echo 'alias k=kubectl' >>~/.bash_profile
echo 'complete -F __start_kubectl k' >>~/.bash_profile
source ~/.bash_profile

# 安装helm
wget https://get.helm.sh/helm-v3.14.2-linux-amd64.tar.gz
tar zvxf helm-v3.14.2-linux-amd64.tar.gz
mv linux-amd64/helm /usr/bin/
rm -rf linux-amd64
rm -f helm-v3.14.2-linux-amd64.tar.gz

# 安装kubectx kubens
git clone https://github.com/ahmetb/kubectx /tmp/kubectx
cp /tmp/kubectx/kubens /usr/bin/kns
cp /tmp/kubectx/kubectx /usr/bin/kctx
wget https://github.com/junegunn/fzf/releases/download/0.29.0/fzf-0.29.0-linux_amd64.tar.gz -P /tmp
tar zvxf /tmp/fzf-0.29.0-llinux_amd64.tar.gz -C /tmp/
mv /tmp/fzf /usr/local/bin/

# 安装kind
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.18.0/kind-linux-amd64
chmod +x ./kind
mv ./kind /usr/local/bin/
echo -e "\n# kind" >> ~/.bash_profile
echo 'source <(kind completion bash)' >>~/.bash_profile

创建集群

其中将apiServerAddress指定为了本机,即创建出来的k8s集群仅允许本集群内访问。如果要是需要多个k8s集群之间的互访场景,由于kind拉起的k8s运行在docker容器中,而docker容器使用的是容器网络,此时如果设置apiserver地址为127.0.0.1,那么集群之间就没法直接通讯了,此时需要指定一个可以在docker容器中访问的宿主机ip地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
if=eth0
ip=`ifconfig $if|grep inet|grep -v 127.0.0.1|grep -v inet6|awk '{print $2}'|tr -d "addr:"`
cat > kind.conf <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: kind
nodes:
- role: control-plane
# 如果需要 ingress,则需要指定该参数
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
extraPortMappings:
- containerPort: 80
hostPort: 80
protocol: TCP
- containerPort: 443
hostPort: 443
protocol: TCP
# 指定 k8s 版本,默认不指定
# image: kindest/node:v1.23.17
- role: worker
- role: worker
- role: worker
networking:
apiServerAddress: "$ip"
apiServerPort: 6443
EOF
kind create cluster --config kind.conf

如果使用 nginx ingress,额外执行命令 kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml

其他周边工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 安装kustomize
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
mv kustomize /usr/local/bin/

# 安装golang
wget https://go.dev/dl/go1.21.8.linux-amd64.tar.gz -P /opt
tar zvxf /opt/go1.21.8.linux-amd64.tar.gz -C /opt/
mkdir /opt/gopath
echo -e '\n# golang' >> ~/.bash_profile
echo 'export GOROOT=/opt/go' >> ~/.bash_profile
echo 'export GOPATH=/opt/gopath' >> ~/.bash_profile
echo 'export PATH=$PATH:$GOPATH/bin:$GOROOT/bin' >> ~/.bash_profile
source ~/.bash_profile

# 安装controller-gen,会将controller-gen命令安装到GOPATH/bin目录下
go install sigs.k8s.io/controller-tools/cmd/controller-gen@latest

# 安装dlv工具
go install github.com/go-delve/delve/cmd/dlv@latest

安装krew

1
2
3
4
5
6
7
8
9
10
11
12
13
(
set -x; cd "$(mktemp -d)" &&
OS="$(uname | tr '[:upper:]' '[:lower:]')" &&
ARCH="$(uname -m | sed -e 's/x86_64/amd64/' -e 's/\(arm\)\(64\)\?.*/\1\2/' -e 's/aarch64$/arm64/')" &&
KREW="krew-${OS}_${ARCH}" &&
curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/latest/download/${KREW}.tar.gz" &&
tar zxvf "${KREW}.tar.gz" &&
./"${KREW}" install krew
)

echo -e '\n# kubectl krew' >> ~/.bash_profile
echo 'export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"' >> ~/.bash_profile
source ~/.bash_profile

资料

概念

  1. 物理卷PV(Physical Volume):可以是整块物理磁盘或者物理磁盘上的分区
  2. 卷组VG(Volume Group):由一个或多个物理卷PV组成,可在卷组上创建一个或者多个LV,可以动态增加pv到卷组
  3. 逻辑卷LV(Logical Volume):类似于磁盘分区,建立在VG之上,在LV上可以创建文件系统 逻辑卷建立后可以动态的增加或缩小空间
  4. PE(Physical Extent): PV可被划分为PE的基本单元,具有唯一编号的PE是可以被LVM寻址的最小单元。PE的大小是可以配置的,默认为4MB。
  5. LE(Logical Extent): LV可被划分为LE的基本单元,LE跟PE是一对一的关系。

基本操作

lvm相关的目录如果没有按照,在centos下使用yum install lvm2进行安装。

初始磁盘状态如下,/dev/sda上有40g磁盘空间未分配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 40G 2.8G 38G 7% /
devtmpfs 236M 0 236M 0% /dev
tmpfs 244M 0 244M 0% /dev/shm
tmpfs 244M 4.5M 240M 2% /run
tmpfs 244M 0 244M 0% /sys/fs/cgroup
tmpfs 49M 0 49M 0% /run/user/1000

# fdisk -l

Disk /dev/sda: 85.9 GB, 85899345920 bytes, 167772160 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0x000a05f8

Device Boot Start End Blocks Id System
/dev/sda1 * 2048 83886079 41942016 83 Linux

对磁盘的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# fdisk /dev/sda
Welcome to fdisk (util-linux 2.23.2).

Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

# 创建主分区2,空间为1g,磁盘格式为lvm
Command (m for help): n
Partition type:
p primary (1 primary, 0 extended, 3 free)
e extended
Select (default p): p
Partition number (2-4, default 2): 2
First sector (83886080-167772159, default 83886080):
Using default value 83886080
Last sector, +sectors or +size{K,M,G} (83886080-167772159, default 167772159): +1G
Partition 2 of type Linux and of size 1 GiB is set

Command (m for help): t
Partition number (1,2, default 2): 2
Hex code (type L to list all codes): 8e
Changed type of partition 'Linux' to 'Linux LVM'

# 创建主分区3,空间为5g,磁盘格式为lvm
Command (m for help): n
Partition type:
p primary (2 primary, 0 extended, 2 free)
e extended
Select (default p): p
Partition number (3,4, default 3): 3
First sector (85983232-167772159, default 85983232):
Using default value 85983232
Last sector, +sectors or +size{K,M,G} (85983232-167772159, default 167772159): +5G
Partition 3 of type Linux and of size 5 GiB is set

Command (m for help): t
Partition number (1-3, default 3): 3
Hex code (type L to list all codes): 8e
Changed type of partition 'Linux' to 'Linux LVM'

# 保存上述操作
Command (m for help): w
The partition table has been altered!

Calling ioctl() to re-read partition table.

WARNING: Re-reading the partition table failed with error 16: Device or resource busy.
The kernel still uses the old table. The new table will be used at
the next reboot or after you run partprobe(8) or kpartx(8)
Syncing disks.

当前磁盘空间状态如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
# fdisk -l

Disk /dev/sda: 85.9 GB, 85899345920 bytes, 167772160 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0x000a05f8

Device Boot Start End Blocks Id System
/dev/sda1 * 2048 83886079 41942016 83 Linux
/dev/sda2 83886080 85983231 1048576 8e Linux LVM
/dev/sda3 85983232 96468991 5242880 8e Linux LVM

将上述两个lvm磁盘分区创建pv,使用pvremove /dev/sda2可以删除pv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# pvcreate /dev/sda2 /dev/sda3
Physical volume "/dev/sda2" successfully created.
Physical volume "/dev/sda3" successfully created.
[root@localhost vagrant]# pvdisplay
"/dev/sda2" is a new physical volume of "1.00 GiB"
--- NEW Physical volume ---
PV Name /dev/sda2
VG Name
PV Size 1.00 GiB
Allocatable NO
PE Size 0
Total PE 0
Free PE 0
Allocated PE 0
PV UUID MgarF2-Ka6D-blDi-8ecd-1SEU-y2GD-JtiK2c

"/dev/sda3" is a new physical volume of "5.00 GiB"
--- NEW Physical volume ---
PV Name /dev/sda3
VG Name
PV Size 5.00 GiB
Allocatable NO
PE Size 0
Total PE 0
Free PE 0
Allocated PE 0
PV UUID ToKp2T-30mS-0P8c-ZrcB-2lO4-ayo9-62fuDx

接下来创建vg,vg的名字可以随便定义,并将创建的两个pv都添加到vg中,可以看到vg的空间为两个pv之和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# vgcreate vg1 /dev/sda1
# vgdisplay -v
--- Volume group ---
VG Name vg1
System ID
Format lvm2
Metadata Areas 2
Metadata Sequence No 1
VG Access read/write
VG Status resizable
MAX LV 0
Cur LV 0
Open LV 0
Max PV 0
Cur PV 2
Act PV 2
VG Size 5.99 GiB
PE Size 4.00 MiB
Total PE 1534
Alloc PE / Size 0 / 0
Free PE / Size 1534 / 5.99 GiB
VG UUID XI8Biv-JtUv-tsur-wuvm-IJQz-HLZu-6a2u5G

--- Physical volumes ---
PV Name /dev/sda2
PV UUID MgarF2-Ka6D-blDi-8ecd-1SEU-y2GD-JtiK2c
PV Status allocatable
Total PE / Free PE 255 / 255

PV Name /dev/sda3
PV UUID ToKp2T-30mS-0P8c-ZrcB-2lO4-ayo9-62fuDx
PV Status allocatable
Total PE / Free PE 1279 / 1279

接下来创建lv,并从vg1中分配空间2g

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# lvcreate -L 2G -n lv1 vg1
Logical volume "lv1" created.

# lvdisplay
--- Logical volume ---
LV Path /dev/vg1/lv1
LV Name lv1
VG Name vg1
LV UUID EesY4i-lSqY-ef1R-599C-XTrZ-IcVL-P7W46Q
LV Write Access read/write
LV Creation host, time localhost.localdomain, 2019-06-16 07:29:38 +0000
LV Status available
# open 0
LV Size 2.00 GiB
Current LE 512
Segments 1
Allocation inherit
Read ahead sectors auto
- currently set to 8192
Block device 253:0

接下来给lv1格式化磁盘格式为ext4,并将磁盘挂载到/tmp/lvm目录下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# mkfs.ext4 /dev/vg1/lv1
mke2fs 1.42.9 (28-Dec-2013)
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
Stride=0 blocks, Stripe width=0 blocks
131072 inodes, 524288 blocks
26214 blocks (5.00%) reserved for the super user
First data block=0
Maximum filesystem blocks=536870912
16 block groups
32768 blocks per group, 32768 fragments per group
8192 inodes per group
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912

Allocating group tables: done
Writing inode tables: done
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done

# mkdir /tmp/lvm
[root@localhost vagrant]# mount /dev/vg1/lv1 /tmp/lvm
[root@localhost vagrant]# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 40G 2.9G 38G 8% /
devtmpfs 236M 0 236M 0% /dev
tmpfs 244M 0 244M 0% /dev/shm
tmpfs 244M 4.5M 240M 2% /run
tmpfs 244M 0 244M 0% /sys/fs/cgroup
tmpfs 49M 0 49M 0% /run/user/1000
/dev/mapper/vg1-lv1 2.0G 6.0M 1.8G 1% /tmp/lvm

接下来对lv的空间从2G扩展到3G,此时通过df查看分区空间大小仍然为2g,需要执行resize2fs命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# lvextend -L +1G /dev/vg1/lv1
Size of logical volume vg1/lv1 changed from 2.00 GiB (512 extents) to 3.00 GiB (768 extents).
Logical volume vg1/lv1 successfully resized.

# resize2fs /dev/vg1/lv1
resize2fs 1.42.9 (28-Dec-2013)
Filesystem at /dev/vg1/lv1 is mounted on /tmp/lvm; on-line resizing required
old_desc_blocks = 1, new_desc_blocks = 1
The filesystem on /dev/vg1/lv1 is now 786432 blocks long.

# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 40G 2.9G 38G 8% /
devtmpfs 236M 0 236M 0% /dev
tmpfs 244M 0 244M 0% /dev/shm
tmpfs 244M 4.5M 240M 2% /run
tmpfs 244M 0 244M 0% /sys/fs/cgroup
tmpfs 49M 0 49M 0% /run/user/1000
/dev/mapper/vg1-lv1 2.9G 6.0M 2.8G 1% /tmp/lvm

问题

试验时操作失误,出现了先格式化磁盘,后发现pv找不到对应设备。vg删除不成功。

正常删除vg的方式,此时lv会自动消失

1
2
vgreduce --removemissing vg1
vgremove vg1

lvremove操作执行的时候经常会出现提示“Logical volume xx contains a filesystem in use.”的情况,该问题一般是由于有其他进程在使用该文件系统导致的。网络上经常看到的是通过fuser或者lsof命令来查找使用方,但偶尔该命令会失效,尤其在本机上有容器的场景下。另外一个可行的办法是通过 grep -nr "/data" /proc/*/mount 命令,可以找到挂载该目录的所有进程,简单有效。

三种Logic Volume

LVM的机制可以类比于RAID,RAID一个核心的机制是性能和数据冗余,并提供了多种数据的冗余模块可供配置。lvm在性能和数据冗余方面支持如下三种Logic Volume: 线性逻辑卷、条带化逻辑卷和镜像逻辑卷。上述几种模式是在vg已经创建完成后创建lv的时候指定的模式,会影响到lv中的pe分配。

下面使用如下的机器配置来进行各个模式的介绍和功能测试。

1
2
3
4
5
6
7
8
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
vda 253:0 0 40G 0 disk
└─vda1 253:1 0 40G 0 part /
vdb 253:16 0 100G 0 disk
vdc 253:32 0 200G 0 disk
vdd 253:48 0 300G 0 disk
vde 253:64 0 400G 0 disk

线性逻辑卷 Linear Logic Volume

当一个VG中有两个或者多个磁盘的时候,LV分配磁盘容量的时候是按照VG中的PV安装顺序分配的,即一个PV用完后才会分配下一块PV。也可以在创建LV的通过指定PV中的PE段来将数据分散到多个PV上。该模式也是LVM的默认模式。

使用/dev/vdb和/dev/vdc两块磁盘来进行测试,先创建对应的PV。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ pvcreate /dev/vdb /dev/vdc
Physical volume "/dev/vdb" successfully created.
Physical volume "/dev/vdc" successfully created.

$ pvdisplay
"/dev/vdb" is a new physical volume of "100.00 GiB"
--- NEW Physical volume ---
PV Name /dev/vdb
VG Name
PV Size 100.00 GiB
Allocatable NO
PE Size 0
Total PE 0
Free PE 0
Allocated PE 0
PV UUID 8NnlYc-4f3f-fkeW-a3l3-LXoC-9UEH-fvpb5V

"/dev/vdc" is a new physical volume of "200.00 GiB"
--- NEW Physical volume ---
PV Name /dev/vdc
VG Name
PV Size 200.00 GiB
Allocatable NO
PE Size 0
Total PE 0
Free PE 0
Allocated PE 0
PV UUID I9ffpN-c1vc-PQOB-yKyd-MdzO-Ngff-6e116t

创建VG vg1,该容量为上面两个磁盘空间之和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ vgcreate vg1 /dev/vdb /dev/vdc
Volume group "vg1" successfully created

$ vgdisplay
--- Volume group ---
VG Name vg1
System ID
Format lvm2
Metadata Areas 2
Metadata Sequence No 1
VG Access read/write
VG Status resizable
MAX LV 0
Cur LV 0
Open LV 0
Max PV 0
Cur PV 2
Act PV 2
VG Size 299.99 GiB
PE Size 4.00 MiB
Total PE 76798
Alloc PE / Size 0 / 0
Free PE / Size 76798 / 299.99 GiB
VG UUID Gi0bJx-jqY8-YpSo-kB0l-9wdk-ZfCT-GpgFZY

从vg1中创建LV lv1,其大小为vg的全部大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ lvcreate --type linear -l 100%VG -n lv1 vg1
Logical volume "lv1" created.

$ lvdisplay
--- Logical volume ---
LV Path /dev/vg1/lv1
LV Name lv1
VG Name vg1
LV UUID JQZ193-dz6A-I0Ue-rTKC-6XrQ-gb1F-Qy9kDl
LV Write Access read/write
LV Creation host, time iZt4nd6wiprf8foracovwqZ, 2022-01-08 23:28:45 +0800
LV Status available
# open 0
LV Size 299.99 GiB
Current LE 76798
Segments 2
Allocation inherit
Read ahead sectors auto
- currently set to 256
Block device 252:0

$ lvs -o lv_name,lv_attr,lv_size,seg_pe_ranges
LV Attr LSize PE Ranges
lv1 -wi-a----- 299.99g /dev/vdc:0-51198
lv1 -wi-a----- 299.99g /dev/vdb:0-25598

条带化逻辑卷 Striped Logic Volume

类似于raid0模式,在该模式下,多块磁盘均会分配PE给LV。可以通过-i参数来指定可以使用VG中多少个PV。

优点:将数据的读写压力分散到了多个磁盘,可以提升读写性能。
缺点:一个磁盘损坏后会导致数据丢失。

但在使用striped模式时,需要注意:

  1. 如果PV来自于同一个磁盘的不同分区,会导致更多的随机读写,不仅不能提升磁盘性能,反而会导致性能下降。
  2. 如果VG中某一个PV过小,则无法将所有的PV平均使用起来,存在木桶效应。

使用--type striped来指定为striped模式,--stripes来指定需要使用的pv数量,--stripesize指定写足够数量的数据后再更换为另外一个pv。在下面的创建命令中,可以看到创建出来了12800个LE,PE是平均分配到了两个pv上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ lvcreate --type striped --stripes 2 --stripesize 32k -L 50G -n lv1 vg1
Logical volume "lv1" created.

$ lvs -o lv_name,lv_attr,lv_size,seg_pe_ranges
LV Attr LSize PE Ranges
lv1 -wi-a----- 50.00g /dev/vdb:0-6399 /dev/vdc:0-6399

$ lvdisplay
--- Logical volume ---
LV Path /dev/vg1/lv1
LV Name lv1
VG Name vg1
LV UUID FnOmCM-IkuE-3Rvv-fQqk-Cv1Q-lb8S-l5X7O3
LV Write Access read/write
LV Creation host, time iZt4nd6wiprf8foracovwqZ, 2022-01-09 00:07:08 +0800
LV Status available
# open 0
LV Size 50.00 GiB
Current LE 12800
Segments 1
Allocation inherit
Read ahead sectors auto
- currently set to 256
Block device 252:0

如果指定的lv大小为250G,由于分配到两块磁盘上,由于最小的磁盘只有100G,100G * 2无法满足250G的磁盘大小需求,此时会报错。

1
2
lvcreate --type striped --stripes 2 --stripesize 32k -L 250G -n lv1 vg1
Insufficient suitable allocatable extents for logical volume lv1: 12802 more required

镜像逻辑卷 Mirror Logic Volume

类似于raid1。可以解决磁盘的单点问题,一块磁盘挂掉后不至于丢失数据。

通过使用--type mirror来指定为镜像模式,-m参数来指定冗余的数量。

常见问题

报错 “Device /dev/sdd excluded by a filter.”

当执行 pvcreate /dev/sdd 命令是报错信息如下:

1
Device /dev/sdd excluded by a filter.

检查下 /etc/lvm/lvm.conf 文件中的 filter 字段是否将磁盘过滤掉了。

执行 pvcreate /dev/sdd -vvv可以查看更详细的报错信息。

可以通过执行wipefs -a /dev/sdd 命令后再执行 pvcreate。

ref

《金字塔原理》是一本教你如何清晰的思考问题,表达观点的畅销书籍,但其并非万能的,其层层架构建立在因果关系基础之上。但现实世界中的很多问题并非简单的因果关系,此时金字塔原理就用不上了。

什么是金字塔原理

定义:任何一件事情都可以归纳出一个中心论点,而这个中心论点由3到7个论据进行支撑。每一个论据它本身又可以拆分成一个论点,该论点同样也可以拆分成3到7个论据,如此重复形状就像金字塔一样。

三个注意点:

  • 结论先行
  • 每层结论下面的论据不要超过7个
  • 每一个论点要言之有物,有明确的思想

组织思想的方法

在有了论点之后,论据该怎么拆分呢?可以按照下面四个原则来组织思想:

  • 时间顺序
  • 空间顺序
  • 重要性顺序,比如按照老弱优先原则
  • 逻辑演绎顺序。所以的逻辑演绎,就是三大段:大前提、小前提和结论。比如,凡人皆有一死,苏格拉底是人,所以苏格拉底也会死。但不推荐用此方法,因为对于听众的要求比较高,必须能够集中注意力才能听明白。

在思考的过程中,如果还没有论点,也可以使用上述方法先得出论据,然后推理出论点。
梳理出的论据必须符合MECE法则:每一个论点下的论据,都应该相互独立,但又可以完全穷尽,即论据要做到不遗漏不重叠。

如何让别人对自己的观点感兴趣

SCQ法则:Situation(设置背景)、Complication(冲突)和Question(疑问)。背景即先介绍大家都认同的背景信息从而引入话题。在SCQ都讲完后,就可以引入自己的论点了。

参考

  • 得到 - 《金字塔原理》成甲解读

最近因为《致阿里》的缘故,读了不少阿里内网的热帖,很多都是洋洋洒洒几千字,而且说的有理有据。如果换做是我,很多帖子哪怕我憋上一天都是写不出来的。我一直在思考,我到底比别人差在了哪里。思来想去,其中一个原因是因为平时的思考总结不够,缺少积累。

恰巧看了张一鸣在字节跳动9周年演讲,三观跟我特别合,想以贴为例,一来分享一下我个人的内心想法加深对演讲内容的认识,二来可以依次来锻炼自己的逻辑归纳能力。后续如有特别值得学习的演讲,也会分享一下自己的学习和思考,比如2020年张小龙的微信公开课的演讲就特别值得学习。

文中提到最多的词莫过于“平常心”了,“平常心”是一个佛源词,看来张一鸣没少研究佛学,整篇演讲显得也比较佛系,对于平常心的最直白的解释就是:

| 吃饭的时候好好吃饭,睡觉的时候好好睡觉

要想做到“好好吃饭,好好睡觉”对我来说是挺难的,我举一个简单的例子。平常周末来说,特别想睡一个懒觉,如果在睡觉前自己明确知道因为工作没有完成,第二天早上会有人找我,那么第二天早上一定会醒的比较早,想睡个懒觉都很难,即使第二天上午并不一定有人在我醒之前找到我。说明因为有事情的缘故,已经在无形中影响了睡眠质量。再举个例子,春节假期的睡眠质量明显会高于平常周末的睡眠质量,原因是心里总有各种工作的事情不能完全放下。如果将春节假期的心态为平常心,那么一年中的其他时间对我而言都不是平常心。

每家公司在年初的时候总会定义一些目标,比如全年营收目标为1个亿。一旦有了营收目标后,那么大家的工作重心一定会围绕的着目标展开。因为毕竟公司的资源是有限的,但是为了达成营收的目标,并不能保持一颗“平常心”来工作,焦虑的员工很难打磨出最好的产品,往往其他的地方就不会做的太好,比如用户体验、代码质量等等,一些本来很好的点子因为有营收目标的缘故,也很难展开实施,从而导致一些创新项目的流失。

接下来就是一些平常心的工作原则,总结下来有如下几点:

  • 平常心对待自己,平常人做非常事
  • 平常心对待预期,没有预期和标签的束缚会发挥的更好
  • 平常心对待过去和未来,关注当下
  • 平常心对待竞争对手
  • 平常心对待业务
  • 平常心对待成功和失败

文中特意提到了互联网八股文的一段话,这里就不再摘出来,我平时也没少见到类似的话术,看完后的感觉就是真牛逼,打死我都写不出来,但转头就忘记讲啥了,也许就是当时压根就看不懂,因为这些话太抽象了。互联网行业本身是一个特别务实接地气的行业,现在也渐渐在内卷严重,抽象的词汇也越来越多,期望下一个风口的到来,或许会将这股邪气吹走一些。

引用

张一鸣演讲全文:外部波澜起伏,内心平静如常

异常处理分为error和defer和recover两类,其中error用来处理可预期的异常,recover用来处理意外的异常。

error

支持多个返回值,可以将业务的返回值和错误的返回值分开,很多都会返回两个值。如果不使用error返回值,可以用_变量来忽略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// parseConfig returns a parsed configuration for an Azure cloudprovider config file
func parseConfig(configReader io.Reader) (*Config, error) {
var config Config

if configReader == nil {
return &config, nil
}

configContents, err := ioutil.ReadAll(configReader)
if err != nil {
return nil, err
}
err = yaml.Unmarshal(configContents, &config)
if err != nil {
return nil, err
}

// The resource group name may be in different cases from different Azure APIs, hence it is converted to lower here.
// See more context at https://github.com/kubernetes/kubernetes/issues/71994.
config.ResourceGroup = strings.ToLower(config.ResourceGroup)
return &config, nil
}


error的几种使用方式:

使用error的方式 说明 举例
errors.New 简单静态字符串的错误,没有额外的信息 errors.New(“shell not specified”)
fmt.Errorf 用于格式化的错误字符串 fmt.Errorf(“failed to start kubernetes.io/kube-apiserver-client-kubelet certificate controller: %v”, err)
实现Error()方法的自定义类型 客户段需要检测并处理该错误时使用该方式 见下文自定义error
Error wrapping Go 1.13支持的特性

errors.New

原则:

  • 不要在客户端判断error中的包含字符串信息。
BadGood
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// package foo

func Open() error {
return errors.New("could not open")
}

// package bar

func use() {
if err := foo.Open(); err != nil {
if err.Error() == "could not open" {
// handle
} else {
panic("unknown error")
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// package foo

var ErrCouldNotOpen = errors.New("could not open")

func Open() error {
return ErrCouldNotOpen
}

// package bar

if err := foo.Open(); err != nil {
if errors.Is(err, foo.ErrCouldNotOpen) {
// handle
} else {
panic("unknown error")
}
}

当然也可以使用自定义error类型,但此时由于要实现自定义error类型,代码量会增加。

自定义error

error是个接口,可以用来扩展自定义的错误处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// file: k8s.io/kubernetes/pkg/volume/util/nestedpendingoperations/nestedpendingoperations.go

// NewAlreadyExistsError returns a new instance of AlreadyExists error.
func NewAlreadyExistsError(operationName string) error {
return alreadyExistsError{operationName}
}

// IsAlreadyExists returns true if an error returned from
// NestedPendingOperations indicates a new operation can not be started because
// an operation with the same operation name is already executing.
func IsAlreadyExists(err error) bool {
switch err.(type) {
case alreadyExistsError:
return true
default:
return false
}
}

type alreadyExistsError struct {
operationName string
}

var _ error = alreadyExistsError{}

func (err alreadyExistsError) Error() string {
return fmt.Sprintf(
"Failed to create operation with name %q. An operation with that name is already executing.",
err.operationName)
}

还可以延伸出更复杂一些的树形error体系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// package net

type Error interface {
error
Timeout() bool // Is the error a timeout?
Temporary() bool // Is the error temporary?
}



type UnknownNetworkError string

func (e UnknownNetworkError) Error() string

func (e UnknownNetworkError) Temporary() bool

func (e UnknownNetworkError) Timeout() bool

Error Wrapping

error类型仅包含一个字符串类型的信息,如果函数的调用栈信息为A -> B -> C,如果函数C返回err,在函数A处打印err信息,那么很难判断出err的真正出错位置,不利于快速定位问题。我们期望的效果是在函数A出打印err,能够精确的找到err的源头。


为了解决上述问题,需要error类型在函数调用栈之间传递,有如下解决方法:


使用fmt.Errorf()来封装error信息,基于已经存在的error再产生一个新的error类型,需要避免error中包含冗余信息。

BadGood
1
2
3
4
5
6
// err: failed to call api: connection refused
s, err := store.New()
if err != nil {
return fmt.Errorf(
"failed to create new store: %s", err)
}
1
2
3
4
5
6
// err: call api: connection refused
s, err := store.New()
if err != nil {
return fmt.Errorf(
"new store: %s", err)
}
1
2
failed to create new store: failed to call api: connection refused
error中会有很多的冗余信息
1
2
new store: call api: connection refused
error中没有冗余信息,同时包含了调用栈信息

但使用fmt.Errorf()来全新封装的error信息的缺点也非常明显,丢失了最初的err信息,已经在中间转换为了全新的err。

类型断言

类型转换如果类型不正确,会导致程序crash,必须使用类型判断来判断类型的正确性。

BadGood
1
t := i.(string)
1
2
3
4
t, ok := i.(string)
if !ok {
// handle the error gracefully
}

panic

用于处理运行时的异常情况。
image.png
使用原则

  • 不要使用panic,在kubernetes项目中几乎没有使用panic的场景
  • 即使使用panic后,一定要使用recover会捕获异常
  • 在测试用例中可以使用panic
BadGood
1
2
3
4
5
6
7
8
9
10
func run(args []string) {
if len(args) == 0 {
panic("an argument is required")
}
// ...
}

func main() {
run(os.Args[1:])
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func run(args []string) error {
if len(args) == 0 {
return errors.New("an argument is required")
}
// ...
return nil
}

func main() {
if err := run(os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

client-go

client-go利用队列来进行重试


https://github.com/kubernetes/client-go/blob/master/examples/workqueue/main.go#L93

kube-builder

kube-builder为client-go的更上次封装,本质上跟client-go利用队列来进行重试的机制完全一致。

发生了错误后该如何处理

  • 打印错误日志
  • 根据业务场景选择忽略或者自动重试
  • 程序自己crash

如何避免

  • 在编写代码时增加防御式编程意识,不能靠契约式编程。一个比较简单的判断错误处理情况的方法,看下代码中if语句占用的比例。https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/kubelet_volumes.go
  • 需求的评估周期中,不仅要考虑到软件开发完成的时间,同时要考虑到单元测试(单元测试用例的编写需要较长的时间)和集成测试的时间
  • 单元测试覆盖率提升,测试场景要考虑到各种异常场景

之前购买的网件R6400V2路由器刷到了梅林系统,但一直以来信号都特别差,甚至都比不过最便宜的水星路由器,想重新刷回官方系统看下是否是梅林系统的问题。本文记录下重新刷回梅林系统的操作步骤。

刷机之路

从如下的地址下载固件。

1
链接:https://pan.baidu.com/s/1EBvUBlXozo_4zkXaeUMQng 密码:pqqv

在梅林系统的界面上找到固件升级的地方,将下载的固件上传。

image.png
结果悲剧的事情发生了,路由器出现了不断重启的状态,并且没有无线信号。猜测可能是因为固件用的是R6400,而非R6400V2导致的。或者是因为是用无线网络升级的原因,导致升级到一半网断掉了,从而导致失败了。

救砖之路

按照网件通用救砖,超详细教程文档中的网件通用救砖方法,将路由器的一个LAN口跟电脑的网卡用网线直接相连,并设置网卡的ip地址为192.168.1.3,网关为255.255.255.0。

在路由器启动的状态下,在命令行执行ping -t 192.168.1.1来验证路由器是否可以ping通,如果不通,可能是因为路由器的LAN段不是192.168.1.0,可能是192.168.50.0的。

在windows电脑上下载对应的文件,并保存到本地的F盘下。

手工安装winpcap,主要是给nmrpflash来使用。

以管理员身份运行命令行工具,执行nmrpflash.exe -L后找到本机的网卡net1。
image.png
执行nmrpflash命令后,立即重启路由器,如果第一次提示Timeout,可以立即执行该命令,如果出现下图的提示,说明命令执行成功。
image.png

参考文档

MTU

MTU是指一个以太网帧能够携带的最大数据部分的大小,并不包含以太网的头部部分。一般情况下MTU的值为1500字节。

当指定的数据包大小超过MTU值时,ip层会根据当前的mtu值对超过数据包进行分片,并会设置ip层头部的More Fragments标志位,并会设置Fragment offset属性,即分片的第二个以及后续的数据包会增加offset,第一个数据包的offset值为0。接收方会根据ip头部的More Fragment标志位和Fragment offset属性来进行切片的重组。

如果手工将发送方的MTU值设置为较大值,比如9000(巨型帧),如果发送方设置了不分片(ip头部的Don’t fragment),此时如果发送的链路上有地方不支持该MTU,报文就会被丢弃。

offload特性

执行 ethtool -k ${device} 可以看到很多跟网络接口相关的特性,这些特性的目的是为了提升网络的收发性能。TSO、UFO和GSO是对应网络发送,LRO、GRO对应网络接收。

执行ethtool -K ${device} gro off/on 来开启或者关闭相关的特性。

LRO(Large Receive Offload)

通过将接收的多个tcp segment聚合为一个大的tcp包,然后传送给网络协议栈处理,以减少上层网络协议栈的处理开销。

但由于tcp segment并不是在同一时刻到达网卡,因此组装起来就会变得比较困难。

由于LRO的一些局限性,在最新的网络上,该功能已经删除。

GRO(Generic Receive Offload)

GRO是LRO的升级版,正在逐渐取代LRO。运行与内核态,不再依赖于硬件。

RSS hash 特性

网卡可以根据数据包放到不同的网卡队列来处理,并可以根据不同的数据协议来设置不同的值。

注意:该特性并非所有的网卡都支持

下面命令为查询 udp 协议的设置,可以看到 hash 的策略为根据源 ip 地址和目的 ip 地址。

1
2
3
4
$ ethtool -n eth0 rx-flow-hash udp4
UDP over IPV4 flows use these fields for computing Hash flow key:
IP SA
IP DA

可以使用 ethtool -N eth0 rx-flow-hash udp4 sdfn来修改hash 策略,sdfn对应的含义如下:

1
2
3
4
5
6
7
8
9
m   Hash on the Layer 2 destination address of the rx packet.
v Hash on the VLAN tag of the rx packet.
t Hash on the Layer 3 protocol field of the rx packet.
s Hash on the IP source address of the rx packet.
d Hash on the IP destination address of the rx packet.
f Hash on bytes 0 and 1 of the Layer 4 header of the rx packet.
n Hash on bytes 2 and 3 of the Layer 4 header of the rx packet.
r Discard all packets of this flow type. When this option is
set, all other options are ignored.

修改完成后再查看网卡的 hash 策略如下:

1
2
3
4
5
6
$ ethtool -n eth0 rx-flow-hash udp4
UDP over IPV4 flows use these fields for computing Hash flow key:
IP SA
IP DA
L4 bytes 0 & 1 [TCP/UDP src port]
L4 bytes 2 & 3 [TCP/UDP dst port]

参考文章

望京SOHO

又是很长的一段时间没有更新,果然又是不定期更新,文章的有些内容也是很久以前积累的,并不是因为太懒,而是确实没有太多的精力。

题图为雨中的望京SOHO,今年全国的雨水特别多,北京亦是如此。南方的鱼米之乡地区出现了严重的洪灾,不知道今年的粮食产量会受多大影响。我们的地球在人类翻天覆地的变更后实在经受不了太多的hack,愿雨季早日过去。

资源

1.bocker

bocker=bash + docker,其利用100多行bash代码实现的简易版的docker,使用到的底层技术跟docker是一致的,包括chroot、namespace、cgroup。

2.kubectx

天天操作k8s的工程师一定少不了使用kubectl命令,而用kubectl命令的工程师一定会特别烦天天输入-n ${namespace}这样的操作,该工具可以省去输入namespace的操作。刚开始的时候不是太习惯该工具,直到近期才感知到该工具的价值。🤦‍♂️

3.KubeOperator

https://raw.githubusercontent.com/KubeOperator/website/master/images/kubeoperator-ui.jpg

k8s集群的安装操作基本上都是黑屏来完成的,同时集群规模较大时,还需要一些自动化的手段来解决安装和运维物理机的问题。KubeOperator提供了界面化的操作来完成k8s集群的配置、安装、升级等的操作,底层也是调用了ansible来作为自动化的工具。该项目已经加入CNCF,期望后面可以做的功能更加强大,给k8s集群的运维带来便利。

4.awesome-operators

k8s生态的operator非常火爆,作为k8s扩展能力的一个重要组成部分,该项目汇总了常见的operator项目。

5.chaos-mesh

pingcap开源的Kubernetes的混沌工程项目,可以使用CRD的方式来注入故障到Kubernetes集群中。

6.devops-exercises

DevOps相关的一些面试题,涉及到的方面还是比较全的。

7.shell2http

可以将shell脚本放到业务页面上执行的工具,在web页面上点击按钮后,会执行shell脚本,shell脚本的输出会在web页面上显示。

8.Google Shell 风格指南

Google编程规范还是比较有权威性的,此为Shell的编码规范。

9.shellcheck

Shell作为弱类型的编程语言,稍有不慎还是非常容易写错语法的,至少很多的语法我是记不住的,每次都是边查语法边写🤦‍。该项目为Shell的静态检查工具,用来检查可能的语法错误,在Github上的start数量还是非常高的。

不仅支持命令行工具检查,而且还可以跟常用的编辑器集成(比如vim、vscode),用来实现边写边检查的效果。还提供了web界面,可以将shell脚本输入到web界面上来在线检查。

10.teambition

阿里的一款的远程协作工具,类似于国外slack+trello的结合版,在产品设计上能看到太多地方借鉴了trello,非常像是trello的本土化版本,更贴近国人的使用习惯,可用于管理团队和个人的任务。

11.IcePanel

IcePanel为vscode的一款插件,提供了k8s一些基础对象的编辑生成器,通过ui的界面即可生成k8s的ConfigMap、Deployment、Service等对象。

  1. Play with Kubernetes

一个提供在线的kubernetes集群的工具,在界面上点一下按钮就可以创建一个k8s集群,不需要注册,非常方便,但创建的集群只有四个小时的使用时间。可以用来熟悉k8s的基本操作,或者试验一些功能。

精彩文章

1.腾讯自研业务上云:优化Kubernetes集群负载的技术方案探讨

k8s虽然在服务器的资源利用率上比起传统的物理机或虚拟机部署服务方式有了非常大的提升,本文结合实践经验,从pod、node、hpa等多个维护来优化以便进一步的压榨服务器的资源。

书籍

1.[Linux开源网络全栈详解:从DPDK到OpenFlow])(http://product.china-pub.com/8061094)

该书可以作为全面了解开源软件网络的相关技术,涉及到Linux虚拟网络、DPDK、OpenStack、容器相关网络等知识。

2.Kubernetes 网络权威指南:基础、原理与实践

该书可以作为全面了解k8s相关的容器网络的相关技术,如果对k8s周边的虚拟网络知识有所全面了解,该书籍还是比较适合的。

在k8s中的内置资源很多都有status部分,比如deployment,用来标识当前资源的状态信息。同样在CRD的体系中,也都有status部分。这些status部分信息,是由operator来负责维护的。

如果直接采用kubectl edit的方式来修改status部分信息,会发现是无法直接修改status部分的,因为status是无法修改成功的,因为status部分是CR的一个子资源。

可以通过如下的方式来完成修改

  1. 首先要准备一个完整的yaml文件,包含了status部分信息

这个的格式必须为json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"apiVersion": "kuring.me/v1alpha1",
"kind": "Certificate",
"metadata": {
"name": "test",
"namespace": "default"
},
"spec": {
"secretName": "test"
},
"status": {
"phase": "pending"
}
}
  1. 获取系统的TOKEN信息

通常在kube-system下会有admin的ServiceAccount,会有一个对应的Secret来存放该ServiceAccount的token信息。执行kubectl get secret -n kube-system admin-token-r2bvt -o yaml获取到token信息,并其中的token部分进行base64解码。

  1. 执行如下的脚本

需要将其中的变量信息修改一下

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash

obj_file=$1
kind=certificates
APISERVER=https://10.0.0.100:6443
namespace=default
TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi10b2tlbi1yMmJ2dCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJhZG1pbiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImRiY2IyNzUzLWE5OGMtMTFlYS04NGVjLTAwMTYzZTAwOGU3MCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlLXN5c3RlbTphZG1pbiJ9.tX3jyNh-GEuZQg-hmy7igqh9vpTAz8Jh9uEv-diZ5XWjX9JYhxwD9nxTQvCcvzY7iPIbvxQfW2GHDZISPoopX0vQy9mQ7npVitrOvFovk06plefI5Gxjdft6vdpt-ArsGTpm7-s9G-3aBg5x41h3Cdgyv-W-ypFlCr9dKu9K7BcRIXSq_GQlq5TBmd-LKFXoer4QGwkn7geq5-ziMk_lY21jIGVdIkq9IRiH8NWuCl7l8i6nQESQDUUpMyKDCqkJqUFV8UkrQL7TfqurFP36_TUAQTh2ZAE8nFnrKRoa09BnjT-FoPO6Jnq6COQjk3PGDHV8LKNDAjCCrs0A53IYGw
obj=nginx-test

echo "begin to patch $obj the file "${obj_file}
curl -XPATCH -H "Accept: application/json" -H "Content-Type: application/merge-patch+json" --header "Authorization: Bearer $TOKEN" --insecure -d @${obj_file} $APISERVER/apis/kuring.me/v1alpha1/namespaces/${namespace}/${kind}/$obj/status

查看网桥设备以及端口

使用brctl show可以查看本地上的所有的网桥设备以及接到网桥设备上的所有网络设备。

查看网桥设备的mac地址表

执行brctl showmacs ${dev},常用来排查一些包丢在网桥上的场景。
其中port no为网桥通过mac地址学习到的某个mac地址所在的网桥端口号。

1
2
3
4
5
6
7
8
9
10
$ brctl showmacs br0
port no mac addr is local? ageing timer
1 02:50:89:59:ac:4b no 3.96
69 02:e2:14:78:d7:92 no 0.57
1 0a:1e:01:dc:67:87 no 10.23
1 0a:60:3c:ca:a8:85 no 6.04
1 0e:01:ce:d6:fc:66 no 8.36
1 0e:0c:f8:6c:08:75 no 56.73
58 0e:49:85:f6:a1:40 no 1.30
22 0e:c0:99:b0:d9:f9 no 0.85

查看网桥设备的某个端口的挂载设备

在上文中中可以获取到某个mac地址对应的网桥设备的端口号,要想知道某个网桥设备的端口号对应的设备可以使用brctl showstp ${dev}命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
brctl showstp br0
br0
bridge id 8000.ae90501b5b47
designated root 8000.ae90501b5b47
root port 0 path cost 0
max age 20.00 bridge max age 20.00
hello time 2.00 bridge hello time 2.00
forward delay 15.00 bridge forward delay 15.00
ageing time 300.00
hello timer 0.03 tcn timer 0.00
topology change timer 0.00 gc timer 62.37
flags
bond0.11 (1)
port id 8001 state forwarding
designated root 8000.ae90501b5b47 path cost 100
designated bridge 8000.ae90501b5b47 message age timer 0.00
designated port 8001 forward delay timer 0.00
designated cost 0 hold timer 0.00
flags
veth02b41ce8 (20)
port id 8014 state forwarding
designated root 8000.ae90501b5b47 path cost 2
designated bridge 8000.ae90501b5b47 message age timer 0.00
designated port 8014 forward delay timer 0.00
designated cost 0 hold timer 0.00
flags
hairpin mode 1
0%