0%

问题背景

gloo 是一个开源的基于 envoy 的云原生 api 网关,其用 K8S 的 CRD 对于网关路由表等进行了一些封装,使得能通过云原生的方式配置 api 网关,提供灵活的 gateway 网络管控。

我们在项目中使用 gloo 的过程中,将其作为最上层的网关,对接了一个 kubernetes apiserver 的 upstream,在对接过程中,遇到了 这个问题,主要是我们发现 envoy 的 ext_authz 模块不支持把 append response header,只支持 set response header,然而 kube-apiserver 有一个 –requestheader-group-headers 的概念,通过这个 Header 能读到用户的 Groups,但是如果采用 envoy 的 ext_authz 模块的话,就会导致最终只读到一个 Group,而不能拿到用户所有的 Groups。

解决方案

1. PR

我给 envoy 官方提了一个 PR,目前已经合并,这个 PR 合并之后,我又给 gloo 项目提了一个 PR,这个 PR 已经被合并了,gloo 1.10 版本终于支持了相关的功能。

2. 低版本打 patch

不幸的是,我们的线上环境还在用 gloo 1.5.5 版本,还没有相关的功能,因此需要手动给 1.5.5 版本打一个小 patch。

为了更方便的打 patch 和使用,我们就没有采取 PR 中的方式来做,而是直接没有考虑兼容性,直接给 Set header 的部分加了一个 append 功能,牺牲了兼容性,但是改起来更加方便了,具体的改动步骤如下

2.1 循线追踪

问题发生在 gateway-proxy 相关的 pod 中,因此我们需要改的是 gateway-proxy 的 image。即 solo-io/gloo-envoy-wrapper 这个镜像。而 solo-io/gloo-envoy-wrapper 的 base 镜像是 solo-io/envoy-gloo,这个镜像的主要源头在 envoy-gloo 这个项目中,下面简单说一下如何查询并做代码改动:

  1. 查看 https://github.com/solo-io/gloo/blob/v1.5.5/Makefile#L32 可以看出,envoy-gloo 镜像版本为 quay.io/solo-io/envoy-gloo:1.16.0-rc4
  2. 查看 https://github.com/solo-io/envoy-gloo/blob/v1.16.0-rc4/bazel/repository_locations.bzl#L4 可以看出 envoy 版本用了 https://github.com/yuval-k/envoy 的 8f4d759cb53493159bcba921d6109eace48cb6da commit
  3. 查看 https://github.com/yuval-k/envoy/blob/8f4d759cb53493159bcba921d6109eace48cb6da/source/extensions/filters/http/ext_authz/ext_authz.cc#L164 更改 setCopy 为 addCopy

2.2 更改 envoy 代码

通过更改 envoy 代码可以形成下面的 patch

1
2
3
4
5
6
7
8
9
10
11
12
13
diff --git a/source/extensions/filters/http/ext_authz/ext_authz.cc b/source/extensions/filters/http/ext_authz/ext_authz.cc
index d6eede1..bb10544 100644
--- a/source/extensions/filters/http/ext_authz/ext_authz.cc
+++ b/source/extensions/filters/http/ext_authz/ext_authz.cc
@@ -179,7 +179,7 @@ void Filter::onComplete(Filters::Common::ExtAuthz::ResponsePtr&& response) {
ENVOY_STREAM_LOG(trace, "ext_authz filter added header(s) to the request:", *callbacks_);
for (const auto& header : response->headers_to_set) {
ENVOY_STREAM_LOG(trace, "'{}':'{}'", *callbacks_, header.first.get(), header.second);
- request_headers_->setCopy(header.first, header.second);
+ request_headers_->addCopy(header.first, header.second);
}
for (const auto& header : response->headers_to_add) {
ENVOY_STREAM_LOG(trace, "'{}':'{}'", *callbacks_, header.first.get(), header.second)

2.3 改动并编译 envoy-gloo 代码

2.3.1 改代码

**把上面的 patch 放到 bazel/external/envoy.patch**,然后需要更改的代码如下:

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
diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl
index 4128f75..129d716 100644
--- a/bazel/repositories.bzl
+++ b/bazel/repositories.bzl
@@ -28,6 +28,10 @@ def _repository_impl(name, **kwargs):
(location["tag"], name),
)

+ if "patches" in location:
+ kwargs["patches"] = location["patches"]
+ kwargs["patch_args"] = ["-p1"]
+
if "commit" in location:
# Git repository at given commit ID. Add a BUILD file if requested.
if "build_file" in kwargs:
diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl
index 6a6509b..99a7801 100644
--- a/bazel/repository_locations.bzl
+++ b/bazel/repository_locations.bzl
@@ -3,6 +3,7 @@ REPOSITORY_LOCATIONS = dict(
envoy = dict(
commit = "51faa5a74f8e19d914078a02acd7b19565b583e8",
remote = "https://github.com/yuval-k/envoy",
+ patches = ["//bazel/external:envoy.patch"],
),
inja = dict(
commit = "4c0ee3a46c0bbb279b0849e5a659e52684a37a98"

2.3.2 编译

建议在镜像中做编译。查看 https://github.com/solo-io/envoy-gloo/blob/v1.16.0-rc4/cloudbuild.yaml#L2 可以看到使用的镜像为 envoyproxy/envoy-build-ubuntu:e7ea4e81bbd5028abb9d3a2f2c0afe063d9b62c0-amd64,然后执行

1
2
3
4
git clone https://github.com/solo-io/envoy-gloo && cd envoy-gloo
git checkout v1.16.0-rc4
docker run --rm -it -v $PWD:/src -w /src \
envoyproxy/envoy-build-ubuntu:e7ea4e81bbd5028abb9d3a2f2c0afe063d9b62c0-amd64 ./ci/do_ci.sh bazel.release

即可以开始编译。注意最好在 Linux 下编译,建议内存为 16Gi。

编译完毕之后,参考 https://github.com/solo-io/envoy-gloo/blob/v1.16.0-rc4/cloudbuild.yaml#L12 的命令得知 linux/amd64/build_release_stripped/envoy 是实际使用的二进制包,利用下述命令打镜像

1
2
cp -f linux/amd64/build_release_stripped/envoy ci/envoy.stripped
TAGGED_VERSION=v1.16.0-rc4-patch make docker-release

如果 docker build 太慢,可以考虑把 ci/Dockerfile 改为下述的形式,其中 1.16.0-rc4 根据版本来选择

1
2
FROM quay.io/solo-io/envoy-gloo:1.16.0-rc4
ADD envoy.stripped /usr/local/bin/envoy

可以看到打了 quay.io/solo-io/envoy-gloo:1.16.0-rc4-patch 镜像,注意这里会自动执行 push 操作,其实是 push 不上去的,可以忽略 push 的报错。

2.4 打 gloo-envoy-wrapper 镜像

1
2
3
4
git clone https://github.com/solo-io/gloo && cd gloo
git checkout v1.5.5
ENVOY_GLOO_IMAGE=quay.io/solo-io/envoy-gloo:1.16.0-rc4-patch make gloo-envoy-wrapper-docker
docker tag quay.io/solo-io/gloo-envoy-wrapper:1.5.5 quay.io/solo-io/gloo-envoy-wrapper:1.5.5-patch

就可以得到 quay.io/solo-io/gloo-envoy-wrapper:1.5.5-patch 镜像了,这个镜像就可以用于启动 gateway-proxy 了。

总结

总的来说,本文主要目的没有讲述 gloo 的 PR 相关的工作,着重点讲述了 gloo gateway-proxy 镜像如何制作的,希望通过本文,让大家能了解到 gloo 和 envoy 直接的一些联系,以及 gloo 是如何在 envoy 上做了一些插件和编译操作。

Bazel 是 Google 开发的一款跨平台编译工具,是内部 blaze 的开源版本。本文不探讨 bazel 的使用,如果想了解如何用 Bazel 构建 go project,可以参考 这篇文章。本文主要探讨 Bazel 开发 go project 和纯使用 go modules 开发会有哪些优劣比较,我们如何去同时利用二者的优劣。

1. Bazel 开发的优势

1.1. 多语言支持

利用 Bazel 开发的 go project 能够支持非常好的可扩展性,对多语言的支持也比较友好,如果是一个公司内部的大项目,希望统一风格,统一 CI,统一构建,那么会存在这种状况:以 go 语言为主,其他语言为辅助。举一个例子,如果你的项目中包含了前端,那么 Bazel 的多语言处理就可以非常方便的让你同时编译 go project 和 frontend project。同理,如果你的项目中有 C++ 等其他语言也可以类似的用 Bazel 统一处理。这样在 monorepo 的管理上会方便很多。

1.2. 便于复用缓存和其他 bazel 的功能

bazel 自身是支持 remote cache 和 remote executor,个人认为 remote cache 功能是比较易用的一个功能。假如我们在 CI 中编译 go 项目,其实很难做到 go remote cache,基本上每次都得重新编译,但是 bazel 可以通过搭建 remote cache 服务重用缓存,极大的加大编译的速度。

另外,如果机器比较多,也可以使用 bazel 的 remote executor 服务搭建代码执行集群,让远端来跑 bazel 代码,这样也可以加速 go 的构建。不过实践下来一般 remote executor 可能不会比 remote executor 加速太多,除非专门对 build cluster 做过优化。

1.3. 镜像打包便捷而可复现

如果是一个 go modules 项目,那么镜像打包我可能就得写这么一个 Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
FROM golang:1.14.2

ENV GOPATH=/go
ENV GOPROXY=https://goproxy.cn,direct
COPY . /go/src/my.project

WORKDIR /go/src/my.project
RUN go build -o main ./

FROM busybox

COPY --from=0 /go/src/my.project/main /usr/local/bin

CMD ["main"]

可以看到,这个 Dockerfile 的问题在于,在 ci 上每次编译都会导致重新编译,不会有任何的缓存,这就回到了上一个问题——无法复用缓存。

但是如果使用 Bazel,我们就可以使用 Bazel rules_docker 的 container_imagecontainer_push 方法,实现镜像的打包和上传。而且 rules_docker 本身是不需要有 docker 环境的,因此我们的 ci 甚至不用装 docker,只需要一个 bazel 就行了

1.4. 第三方包引用和更改比较方便

我自己并不是一个 go modules 深度用户,所以假如我们引用了一个第三方包,要对第三方包打 patch,就会比较麻烦,当然可以说,我们可以使用 vendor,但这并不符合 go modules 的哲学,那这样其实就会比较麻烦。

bazel 利用 gazelle 把所有第三方包显式的声明在 WORKSPACE 中,如果需要改动,只需给这个包加上 .patch 文件的 patch 即可实现更改,这样其实比起 vendor 来说更改也更容易被记录。

2. Bazel 开发相较于 go modules 的劣势

2.1 IDE 提示没有 go modules 友好

众所周知,go modules 很多 IDE 有非常好的支持,例如 vscode 和 goland,对于跳转等待支持得也非常好。但是 Bazel 就难办了,目前我尝试下来,只有 goland(或者 Jetbrains 系列) 的 bazel 插件是比较完善的,跳转基本能支持。

另一个方面,由于 Bazel 的尿性,每次本地执行 bazel build 会删除某些不必要的执行结果,因此可能会导致本地自己执行 bazel build 等指令之后,需要在 goland 中重新 sync 一下,这样会造成一些时间成本。

2.2 Bazel 依赖管理错乱

go modules 有自己一套依赖管理,但是 Bazel 的依赖,很多时候可能都是无脑 gazelle update 添加依赖,不会考虑依赖之间的版本关系,因此会导致 Bazel 项目中的版本依赖很错乱。

2.3 一些 go modules 相关的工具无法支持

一个例子就是 golangci-lint 这个包。这个东西是不支持 Bazel 的,因此只有 go modules 项目可以支持。

2.4 有一定的学习成本

对于团队中的同学来说,如何使用 Bazel,如何添加依赖,是有一定的学习成本的。

3. 同时支持 Bazel 和 go modules

可以看到二者有他的优劣,我们能否合并二者,取长补短,发挥二者的优势呢?答案是肯定的。

我们可以以 bazel 为主,go modules 为辅。以下针对上面说的 Bazel 3 个劣势来说明如何解决。

对于第一个劣势,虽然我个人觉得 goland 的 Bazel 插件其实还行,但是如果实在不能忍受,可以直接使用我们提供的 go.mod 来基于 go modules 开发。

对于第二个劣势,鉴于 bazel-gazelle 的 update 命令有 --from-file=go.mod flag,使得我们可以从 go.mod 中生成 Bazel 版本依赖,这样就可以保证 Bazel 的依赖管理和 go modules 的依赖管理是一致的,不会使得依赖管理错乱,解决了上面的第二个问题。这个技巧在 k8s repo-infra 的 update-deps.sh 中有使用。

对于第三个劣势,我们可以书写一些 Bazel 脚本,在 Bazel 的沙箱中使用 go modules 模式来使用 golangci-lint,这个方式在 k8s repo-infra 的 verify-golangci-lint.sh 中有使用。

对于第四个劣势,就要求项目维护者提供详尽的文档说明,如何 update 依赖,如何处理 Bazel 的一些问题,如何优化项目结构。当然我认为这并不是太大的问题,因为对于一个大项目来说,规范是必须的。

另外,k8s 有使用 Bazel 来构建 go projects,他们也采用的是 go modules 和 Bazel 同时支持的方案来实现。这样对于开发者也非常友好,同时对于大项目的管理、构建和测试也非常的友好。

由此,我书写了一个 bazel-go-scaffold,基于 repo-infra 扩展了一个基于 Bazel 的 go 大项目模板,供大家参考

初始化 fish 和 tmux 配置

一般来说,拿到一台 Linux 服务器,没有好用的 shell,没有好用的 tmux 配置,用起来很让人头疼。特别是有时候不知道 sudo 密码,但是可以不用密码 sudo,设置默认终端也是很麻烦的一件事情。

下面我就分享一下我的开机配置方案:

  • fish 并设置默认终端
  • .tmux.conf

脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# install software-properties-common
sudo apt-get update -y && \
sudo apt install -y software-properties-common

# install fish
sudo apt-add-repository -y ppa:fish-shell/release-3 && \
sudo apt-get update -y && \
sudo apt-get install -y fish && \
sudo rm /etc/apt/sources.list.d/fish-shell-*

# use fish as default shell
command -v fish | sudo tee -a /etc/shells
sudo chsh -s "$(command -v fish)" "${USER}"

# install tmux and tmux config
sudo apt update -y && sudo apt install tmux
cd && git clone https://github.com/gpakosz/.tmux.git && \
ln -s -f .tmux/.tmux.conf && \
cp .tmux/.tmux.conf.local .

退出重新 ssh 服务器,或者重新打开终端,即可有一个非常爽 fish+tmux 环境

更改 .tmux.conf 的 tmux prefix 配置

由于 https://github.com/gpakosz/.tmux.git 的 .tmux 配置可以使用 C-b 也可以使用 C-a,但是作为 emacs 终端快捷键使用者,这两个键分别是左移和回到行首,不是很方便,我一般改成 C-x,更改方案是,进入 ~/.tmux.conf,更改下述两行

1
2
set -g prefix2 C-a                        # GNU-Screen compatible prefix
bind C-a send-prefix -2

1
2
3
4
set -g prefix2 C-x                        # GNU-Screen compatible prefix
bind C-x send-prefix -2
set -g prefix C-x # GNU-Screen compatible prefix
bind C-x send-prefix

即可。

0. 安装 docker 和 docker-compose

安装 docker 请参考

安装 docker 和 k8s 相关命令行工具

同时参考 daocloud 上的 docker-compose 安装文档,可以使用下述语句安装 docker-compose:

1
2
3
curl -L https://get.daocloud.io/docker/compose/releases/download/1.24.1/docker-compose-`uname -s`-`uname -m` > /tmp/docker-compose
chmod +x /tmp/docker-compose
sudo mv /tmp/docker-compose /usr/local/bin/docker-compose

1. 利用 docker-compose 部署单机单 osd ceph

主要参考官方 docker 镜像文档,但最新的 ceph/daemon 镜像似乎有点奇怪的 bug,导致 osd 起不来,因此,我们使用一个 2019年年初的镜像,已经 tag 成 huangwx/ceph-daemon 供使用。ceph 的 docker-compose 文件如下:

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
# docker-compose-ceph.yaml
version: '3.7'
services:
ceph-mon:
image: huangwx/ceph-daemon
network_mode: host
privileged: true
volumes:
- ./ceph:/etc/ceph
- cephdata:/var/lib/ceph
container_name: ceph-mon
command:
- mon
environment:
- MON_IP=${MON_IP}
- CEPH_PUBLIC_NETWORK=${CEPH_PUBLIC_NETWORK}
ceph-mgr:
image: huangwx/ceph-daemon
network_mode: host
privileged: true
volumes:
- ./ceph:/etc/ceph
- cephdata:/var/lib/ceph
container_name: ceph-mgr
command:
- mgr
depends_on:
- ceph-mon
ceph-osd:
image: huangwx/ceph-daemon
network_mode: host
privileged: true
volumes:
- ./ceph:/etc/ceph
- cephdata:/var/lib/ceph
container_name: ceph-osd
command:
- osd
depends_on:
- ceph-mgr
environment:
- OSD_TYPE=directory
pid: host
volumes:
cephdata: {}
networks:
default:
external: true
name: host

docker-compose-ceph.yaml 的相同目录下新建一个 ceph 目录

注意设置 MON_IPCEPH_PUBLIC_NETWORK 两个环境变量,两个环境变量分别为主机 IP 地址,允许访问 ceph 的网段。下面仅作为演示,MON_IP 和 CEPH_PUBLIC_NETWORK 请设置成你自己的:

1
export MON_IP=192.168.2.2 CEPH_PUBLIC_NETWORK=192.168.0.0/16

同时请确认 ./ceph 目录下没有任何 ceph 的配置文件,然后启动 ceph:

1
docker-compose -f docker-compose-ceph.yaml up -d

启动后会在 ./ceph 目录下生成下述3个 ceph 配置文件:

1
2
3
ceph.client.admin.keyring
ceph.conf
ceph.mon.keyring

启动成功之后,稍等片刻,然后执行下述命令 check ceph 是否正常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ docker exec -it ceph-mon ceph -s
cluster:
id: e3cdecce-16f7-4c0f-9909-ab94a5e7c1b5
health: HEALTH_OK

services:
mon: 1 daemons, quorum new
mgr: new(active)
osd: 1 osds: 1 up, 1 in

data:
pools: 0 pools, 0 pgs
objects: 0 objects, 0 B
usage: 2.9 GiB used, 1020 GiB / 1023 GiB avail
pgs:

如果成功看到类似上述的返回,即说明成功。

2. 初始化 rbd 配置

接下来初始化 rbd 配置。由于是单 osd 单 monitor 集群,因此我们需要创建并设置 rbd:

1
2
3
4
docker exec -it ceph-mon ceph osd pool create rbd 64 64 replicated
docker exec -it ceph-mon ceph osd pool application enable rbd rbd
docker exec -it ceph-mon rbd pool init rbd
docker exec -it ceph-mon ceph osd pool set rbd size 1

即可成功初始化并设置 rbd,可以利用 rbd ls 命令 check 是否能够不卡住返回:

1
docker exec -it ceph-mon rbd ls

如果能够成功不卡住返回空值,说明 rbd 集群正常,执行 ceph -s 可以看到初始化了 1 个 pool 和 64 个 pgs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ docker exec -it ceph-mon ceph -s
cluster:
id: e3cdecce-16f7-4c0f-9909-ab94a5e7c1b5
health: HEALTH_OK

services:
mon: 1 daemons, quorum new
mgr: new(active)
osd: 1 osds: 1 up, 1 in

data:
pools: 1 pools, 64 pgs
objects: 0 objects, 0 B
usage: 2.9 GiB used, 1020 GiB / 1023 GiB avail
pgs: 64 active+clean

本文主要参考 Kubernetes 官方文档 Creating a single control-plane cluster with kubeadm (v1.16),但由于国内实在是访问 Google 困难,之前的教程都是从国内各种镜像中拉取 k8s 镜像,再 tag 成官方的,实在是不太优雅。好在 K8S 新版支持了指定镜像源,可以方便的指定国内源。

0. 安装 docker 和 k8s 相关命令行工具

0.1 安装 docker

利用 daocloud 上的 docker 安装方法在国内安装 docker,由于默认会从官方拉,我们可以考虑利用 Aliyun 的镜像

1
curl -sSL https://get.daocloud.io/docker | sh -s -- --mirror Aliyun

然后配置 docker 加速器,这里我们选择网易的 docker 源,如果有其他国内源,你也可以自行更改

1
curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://hub-mirror.c.163.com

根据提示重启 docker

1
sudo systemctl restart docker.service

0.2 安装 k8s 相关命令行工具——kubelet, kubeadm, kubectl

在这里我们利用阿里云的 ubuntu k8s 源下载 k8s 相关包

1
2
3
4
5
6
7
sudo apt-get update && sudo apt-get install -y apt-transport-https curl
curl -s https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | sudo apt-key add -
sudo tee -a /etc/apt/sources.list.d/kubernetes.list > /dev/null <<EOT
deb http://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
EOT

sudo apt install -y kubelet=1.16.2-00 kubeadm=1.16.2-00 kubectl=1.16.2-00

我们这里安装的是目前 K8S 最新的版本 v1.16.2

1. 初始化 Kubernetes

1.1 初始化 k8s master

K8s 1.11 支持了 kubeadm 传入配置文件,我们可以新建下述配置文件 kubeadm.conf,注意我们采用的是 k8s 1.16.1 的相关配置,这个版本支持的 etcd 镜像 tag 为 3.3.15-0,如果有必要,dnsDomain 可以改成自己需要的,这里我们采用默认的 cluster.local

如果你的 k8s 版本不一致,可以采用 kubeadm config images list 查看你的 k8s 所需要的 etcd 镜像 tag。

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: kubeadm.k8s.io/v1beta1
kind: ClusterConfiguration
etcd:
# one of local or external
local:
imageRepository: "registry.aliyuncs.com/google_containers" # origin is "k8s.gcr.io"
imageTag: "3.3.15-0"
networking:
serviceSubnet: "10.96.0.0/12"
podSubnet: "10.244.0.0/16" # for flannel
dnsDomain: "cluster.local"
kubernetesVersion: "v1.16.2"
imageRepository: "registry.aliyuncs.com/google_containers" # origin is "k8s.gcr.io"

这里我们用到了阿里云的 k8s 镜像源。上面的 kubernetesVersion 写明自己的 kubeadm 版本,不然会去 google 官方请求版本,可能会卡住。然后运行

1
kubeadm init --config kubeadm.conf

即可初始化集群。

完成之后,初始化成功,会输出类似于下面的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Your Kubernetes master has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/

You can now join any number of machines by running the following on each node
as root:

kubeadm join 172.17.49.197:6443 --token zq59ch.wrr80vejg49091mw --discovery-token-ca-cert-hash sha256:197810ac44d29e4f311a639b22f1dac7dced81604c489be390a1fa5cf0b96f8d

执行

1
2
3
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

最后,为了让 master 也可以调度,可以加入下面的命令:

1
kubectl taint node $(kubectl get nodes -o name) node-role.kubernetes.io/master-

1.2 添加网络配置

上述信息做好了之后,在 master 上加入 flannel 插件,flannel 插件版本我们目前采用文档中针对 v1.16 的版本,由于国内网络环境缓慢,建议下载该文件更改镜像之后再 kubectl apply:

1
2
curl https://raw.githubusercontent.com/coreos/flannel/2140ac876ef134e0ed5af15c65e414cf26827915/Documentation/kube-flannel.yml | sed 's/quay.io/quay.azk8s.cn/g' > /tmp/kube-flannel.yml
kubectl apply -f /tmp/kube-flannel.yml

注意上述命令只需要在主节点执行。

1.3 节点加入

kubeadm init成功后,命令行会显示 Join 命令,命令格式如下:

1
kubeadm join --token <token> <master-ip>:<master-port> --discovery-token-ca-cert-hash sha256:<hash>

但如果之后忘记了命令,可以用下述命令获取 <token>

1
kubeadm token list

然后可以用下述命令获得 <hash>

1
openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'