MichaelFu

好记性不如烂笔头

本篇文章介绍使用 kubeadm 创建一个多节点的 K8S,使用 containerd 作为容器运行时,第一步,首先是准备 3 个虚拟机节点,使用 multipass 创建3台虚拟机,该镜像中自带 docker,无需再安装,使用如下命令创建:

multipass launch --name ctrlnode -d 40G docke
multipass launch --name node1 -d 40G docker
multipass launch --name node2 -d 40G docker

每个节点至少2GB内存,2个CPU,具体要求请看这里。创建成功之后,如下所示:

1
2
3
4
5
6
7
8
$ multipass list
Name State IPv4 Image
ctrlnode Running 192.168.67.8 Ubuntu 22.04 LTS
172.17.0.1
node1 Running 192.168.67.10 Ubuntu 22.04 LTS
172.17.0.1
node2 Running 192.168.67.9 Ubuntu 22.04 LTS
172.17.0.1

VM 版本如下:

1
2
3
4
5
6
ubuntu@node2:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 22.04.3 LTS
Release: 22.04
Codename: jammy
阅读全文 »

单机场景下,相同主机上的容器会通过 Docker 默认创建的 docker0 网桥以及在启动容器时创建的 veth pair 设备实现互通。而对于跨主机容器通信,社区提供了很多种不同的方案,例如 weaveflannel,本篇文章将以 flannel 为例,实现跨主机容器通信,flannel 有多种后端实现,本文以 VXLAN 为例,动手实践,最终达到的效果如下图所示:

阅读全文 »

Docker 容器通过 Linux 提供的各种 namespace 技术,将运行中的容器封闭在一个沙箱中,看起来很像一个虚拟机,都拥有独立的网络栈,有独立的 IP 地址,但是这些同主机上的独立容器貌似天生互通,能通过各自的 IP 相互访问,这是如何做到的的?

如果我们想要实现两台独立主机之间互通,最简单的办法就是拿一根网线把它们连在一起;想要实现多台主机互通,这个时候就需要一台交换机了。

现在在不同的容器之间,想要实现互通,我们也可以借鉴交换机这种技术,毕竟容器看起来很像独立的主机。在 Linux 中,可以通过网桥(Bridge)模拟交换机,网桥工作是一个二层网络设备,工作在数据链路层,主要功能是能够根据MAC地址将数据包发送到网桥的不同端口上

二层网络和三层网络的主要区别是,二层网络中可以仅靠MAC地址就实现互通,但是三层网络需要通过IP路由实现跨网络互通,这也能看出,二层网络的组网能力非常有限,一般只是小局域网,三层网络可以组建大型网络。

Docker 项目为了实现这种在相同主机上创建容器之间互通的目的,在主机上会创建一个名叫 docker0 的网桥,凡是连接在 docker0 上的容器,就可以通过它进行通信。要把一个容器插在网桥上,需要依赖 Veth Pair 这个虚拟设备了,它的特点是,它被创建出来之后,总是以两张虚拟网卡成对出现,并且从一张网卡发出的数据包,可以直接出现在与它对应的另一张网卡上,即使两张网卡在不同的 namespace 中。一旦一张虚拟网卡被插在了网桥设备上,它就会被降级成网桥的端口,丢失了处理数据包的能力,数据包会全部交由网桥进行处理。

如下是宿主机上 docker0 设备信息,172.17.0.1/16 是 Docker 默认的子网:

1
2
3
4
5
6
7
8
root@michael-host:/home/michael# ip addr show docker0
7: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ad:c7:75:98 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:adff:fec7:7598/64 scope link
valid_lft forever preferred_lft forever
root@michael-host:/home/michael#
阅读全文 »

在学习 Linux 网络相关的知识时或者在定位网络相关的问题中,经常需要使用 route 命令查看路由表,本节主要记录该命令的输出及其含义。Linux 系统上一般有3张路由表,可以通过 ip rule 命令查看:

1
2
3
4
# ip rule list
0: from all lookup local
32766: from all lookup main
32767: from all lookup default

路由表的配置可以通过 ip route list table {name} 输出,如果是查看 main 表,可以直接使用 route -n,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
root:/mnt/e/github/proto# ip route list table main
default via 172.23.32.1 dev eth0
10.42.0.0/24 dev cni0 proto kernel scope link src 10.42.0.1
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown
172.23.32.0/20 dev eth0 proto kernel scope link src 172.23.45.94
root:/mnt/e/github/proto#
root:/mnt/e/github/proto#
root:/mnt/e/github/proto# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.23.32.1 0.0.0.0 UG 0 0 0 eth0
10.42.0.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
172.23.32.0 0.0.0.0 255.255.240.0 U 0 0 0 eth0
10.244.186.193 0.0.0.0 255.255.255.255 UH 0 0 0 cali687d9beb32a

各字段主要说明如下:

  • Destination:目标网络或目标主机;
  • Gateway:网关,连接两个不同网络的设备;
  • Genmask:目的地址的子网掩码。255.255.255.255 表示目的主机,0.0.0.0 表示默认路由,其他情况 GenmaskDestination 组成目标网络;
  • Flags:标识 U 表示路由生效,G 表示网关,H 表示目标地址是一个主机;
  • Metric:到目标地址的跳数;
  • Ref:路由被引用数;
  • Use: 路由被查询次数;
  • Iface:接口,去往目的地址所经出口;

对于第一条路由,目标地址 0.0.0.0,表示没有明确指定的意思,既默认路由。路由匹配是按照掩码长短从长到端开始匹配,默认路由掩码也是 0.0.0.0,表示最后匹配;

对于中间三条路由,Gateway 都是 0.0.0.0,表示本条路由不经网关,网关是从一个网络进入另一个网络的边缘设备。换句话说,命中网关是 0.0.0.0 的报文,它的目标是可能是同一网络下的其它目标地址。这时候走的是二层直连,需要发起 ARP 请求换取 MAC 地址进行发送。这条路由通常是在网卡上配置 IP 时候自动生成的。在网卡上每绑定一个 IP,就相应地生成一条这样的记录。可以看到本条路由的 Flags 并没有 G 标志。

第五条路由,标志为 H,掩码是 255.255.255.255,表示目标地址是 10.244.186.193,直接发往 cali687d9beb32a,而这个设备的另一端是容器内的 eth0。这种情况也不需要网关,网关为 0.0.0.0

阅读全文 »

Protobuf 是 Google 出品的消息编码工具,相比常用的 json 等编码方式,以牺牲可读性,而提高编码效率,减少编码之后消息体占用的字节大小,以提升传输效率。本篇文章主要分享如何生成 Go 语言 pb 版本,对于 Go 语言而言,protoc 不能直接生成 Go 代码,需要额外的插件。对于这个插件,官方有自己的实现,也有第三方的 gogo/protobuf,本节主要是用来厘清他们之间的区别以及用法。在开始之前,我们先澄清一些基本的概念:

  1. golang/protobuf 是官方早期的插件实现;
  2. google.golang.org/protobuf 是上面的继承者,有更新和更简化的 API,以及其他许多改进,是官方当前的实现;
  3. gogo/protobuf 社区实现,该实现目前被废弃,但是在历史中依然后很多著名的软件在使用,例如 etcd
  4. protocprotobuf 的编译器,用于将 .proto 文件编译成各自语言的实现;
  5. protobuf 是一般用于指这门编码语言,该语言目前有两个版本,proto2proto3

关于 protobuf 编码是如何优化编码效率,可以查看这篇文章:Protocol Buffers 编码

阅读全文 »

本篇文章介绍常用的二进制减小方案,某些场景下,对二进制文件的大小有比较严格的要求,尤其是某些便携嵌入式设备上。

代码优化

编码阶段,我们可以从以下几点入手:

  1. 减少使用泛型,考虑使用动态类型替换;但是动态调用相比静态展开有性能损失,需要做权衡;
  2. 合理使用宏;有些宏展开后会生成很多代码,如果不合理使用,例如,某些通用的 log 宏,助手宏,会展开生成很多代码导致二进制文件体积增加;
  3. 合理使用内联函数;一般我们使用内联函数加快代码执行的速度,但过多的内联函数也会导致二进制体积增加;

例如对于泛型和动态类型,这两种方式实现的代码编译之后二进制大小是有差异的,print1 会根据不同类型的参数展开成不同的版本:

1
2
3
4
5
6
7
8
9

fn print1<T: Display>(param: T) {
println!("{:}", param);
}

fn print2(param: &dyn Display) {
println!("{:}", param);
}

阅读全文 »

记录几种用于清理 Docker 磁盘空间占用的几种方式。

  1. 删除不想要的容器、网络、镜像以及构建缓存等,首先使用如下的命令查看 Docker 空间占用:

    docker system df

    1
    2
    3
    4
    5
    6
    root@ctrlnode:/home/ubuntu# docker system df
    TYPE TOTAL ACTIVE SIZE RECLAIMABLE
    Images 15 14 2.332GB 13.26kB (0%)
    Containers 51 22 219B 104B (47%)
    Local Volumes 1 0 0B 0B
    Build Cache 0 0 0B 0B

    如果使用 -v 参数可以查看更具体的每个镜像,容器的占用。使用 docker system prune 命令删除停止状态的容器,未关联容器的网络以及 dangling 镜像,如果使用 -a 参数,还将清除未使用的镜像,-f 表示强制操作:

    docker system prune -a -f

    如果仅仅是删除未使用的和dangling 镜像使用:

    docker image prune -a

    类似的,下面的两条命令用于清除停止的容器以及未使用的卷:

    docker container prune
    docker volume prune

  2. 仅删除 dangling 镜像:

    docker rmi $(docker images -f “dangling=true” -q)

    如果是删除退出的容器:

    docker rm -v $(docker ps -aq -f status=exited)

  3. 清理日志,编辑文件:/etc/docker/daemon.json

    vi /etc/docker/daemon.json

    添加下面这些配置:

    1
    { "log-driver":"json-file", "log-opts": {"max-size":"3m", "max-file":"1"} } 

    重启:

    systemctl daemon-reload systemctl restart docker

  4. k8s 环境的清理,首先驱逐节点:

    kubectl drain this_node --ignore-daemonsets --delete-local-data

    停止 kubelet 服务:

    kubelet stop

    重启 Docker

    service docker restart

    清理 Docker

    docker system prune --all --volumes --force

  5. 相当于重装 Docker

    systemctl stop docker
    rm -rf /var/lib/docker
    systemctl start docker

Rust 的编译速度和跨平台编译相比 Go 语言就要难用很多,但这也是语言特点,当你从中受益时,必然要付出一些代价,本文主要介绍如何实现跨平台编译,使用 cross 这个工具。

我的工作台是 Mac M2,想编译出 LinuxWindows 的可执行文件,使用的代码很简单,就是 Hello World 示例程序,这个不是重点。

使用 cross 首先当然是安装,按照官方的描述,可以使用下面的命令:

1
cargo install cross --git https://github.com/cross-rs/cross

然后是安装 docker 或者 podman,本文以 docker 为例,讲述使用过程中遇到的问题及其解决方案。cross 的使用很简单,例如,如果我要编译 targetaarch64-unknown-linux-gnu,执行:

1
cross build  --target aarch64-unknown-linux-gnu
阅读全文 »

环境准备

以下使用 docker 准备学习环境。

  1. 先拉取centos的最新镜像:docker image pull centos
  2. 创建一个数据卷用于存放容器中产生的文件:docker volume create centos
  3. 启动我们的容器:docker run -d -it -v centos:/workdir --name centos centos /bin/bash
  4. 进入我们的容器:docker exec -it centos /bin/bash

Nginx 的主要应用场景

  1. 静态资源服务,即通过本地文件系统提供服务;
  2. 反向代理服务,提供缓存,负载均衡功能;
  3. API服务,通过Openresty直接访问数据库;
阅读全文 »
0%