记录一次 CentOS AArch64 7 到 8 的升级
背景
有一台 AArch64 机器安装了 CentOS 7,想要升级到 CentOS 8,这篇博客主要讲讲折腾的整个过程,而不是教程:如果真要说,就是不要升级 CentOS 大版本,直接重装吧。如果真的想折腾,可以看看下面的内容。
有一台 AArch64 机器安装了 CentOS 7,想要升级到 CentOS 8,这篇博客主要讲讲折腾的整个过程,而不是教程:如果真要说,就是不要升级 CentOS 大版本,直接重装吧。如果真的想折腾,可以看看下面的内容。
上一次折腾 Gentoo/Prefix 是五年多以前,当时还是用的 Intel Mac,最近需要探索一下在现在的 macOS 系统上用 Gentoo/Prefix 会遇到哪些问题,因此今天在 Apple M1 上重新尝试一次。
给 ESXi 接了两路 10Gbps 的以太网,需要用 LACP 来聚合。ESXi 自己不能配置 LACP,需要配合 vCenter Server 的 Distributed Switch 来配置。
使用 Ceph 做存储的方式:
Ceph 客户端认证需要用户名 + 密钥。默认情况下,用户名是 client.admin
,密钥路径是 /etc/ceph/ceph.用户名.keyring
。ceph --user abc
表示以用户 client.abc
的身份访问集群。
用户的权限是按照服务类型决定的。可以用 ceph auth ls
显示所有的用户以及权限:
$ ceph auth ls
osd.0
key: REDACTED
caps: [mgr] allow profile osd
caps: [mon] allow profile osd
caps: [osd] allow *
client.admin
key: REDACTED
caps: [mds] allow *
caps: [mgr] allow *
caps: [mon] allow *
caps: [osd] allow *
可以看到,osd.0
对 OSD 有所有权限,对 mgr 和 mon 都只有 osd 相关功能的权限;client.admin
有所有权限。profile
可以认为是预定义的一些权限集合。
新建用户并赋予权限:
修改权限:
获取权限:
删除用户:
管理 OSD 实际上就是管理存储数据的硬盘。
查看状态:
显示有多少个在线和离线的 OSD。
显示了存储的层级,其中 ID 非负数是实际的 OSD,负数是其他层级,例如存储池,机柜,主机等等。
CRUSH 是一个算法,指定了如何给 PG 分配 OSD,到什么类型的设备,确定它的 failure domain 等等。例如,如果指定 failure domain 为 host,那么它就会分配到不同 host 上的 osd,这样一个 host 挂了不至于全军覆没。类似地,还可以设定更多级别的 failure domain,例如 row,rack,chassis 等等。
OSD 可以设置它的 CRUSH Location,在 ceph.conf 中定义。
为了配置数据置放的规则,需要设置 CRUSH Rule。
列举 CRUSH Rule:
查看 CRUSH 层级:
在里面可能会看到 default~ssd
,它指的意思就是只保留 default 下面的 ssd 设备。
文本形式导出 CRUSH 配置:
可以看到 Rule 的定义,如:
# simple replicated
rule replicated_rule {
id 0
# a replicated rule
type replicated
# iterate all devices of "default"
step take default
# select n osd with failure domain "osd"
# firstn: continuous
step chooseleaf firstn 0 type osd
step emit
}
# erasure on hdd
rule erasure-hdd {
id 4
# an erasure rule
type erasure
# try more times to find a good mapping
step set_chooseleaf_tries 5
step set_choose_tries 100
# iterate hdd devices of "default", i.e. "default~hdd"
step take default class hdd
# select n osd with failure domain "osd"
# indep: replace failed osd with another
step choose indep 0 type osd
step emit
}
# replicated on hdd
rule replicated-hdd-osd {
id 5
# a replicated rule
type replicated
# iterate hdd devices of "default", i.e. "default~hdd"
step take default class hdd
# select n osd with failure domain "osd"
# firstn: continuous
step choose firstn 0 type osd
step emit
}
# replicated on different hosts
rule replicated-host {
id 6
# a replicated rule
type replicated
# iterate all devices of "default"
step take default
# select n osd with failure domain "host"
# firstn: continuous
step chooseleaf firstn 0 type host
step emit
}
# replicate one on ssd, two on hdd
rule replicated-ssd-primary {
id 7
# a replicated rule
type replicated
# iterate ssd devices of "default"
step take default class ssd
step chooseleaf firstn 1 type host
step emit
# iterate hdd devices of "default"
step take default class hdd
step chooseleaf firstn 2 type host
step emit
}
choose 和 chooseleaf 的区别是,前者可以 choose 到中间层级,例如先选择 host,再在 host 里面选 osd;而 chooseleaf 是直接找到 osd。所以 choose type osd
和 chooseleaf type osd
是等价的。
如果这个搜索条件比较复杂,例如找到了某一个 host,里面的 osd 个数不够,就需要重新搜。
新建一个 Replicated CRUSH Rule:
# root=default, failure domain=osd
ceph osd crush rule create-replicated xxx default osd
# root=default, failure domain=host, class=ssd
ceph osd crush rule create-replicated yyy default host ssd
如果指定了 device class,它只会在对应类型的设备上存储。
Pool 是存储池,后续的 RBD/CephFS 功能都需要指定存储池来工作。
创建存储池:
为了性能考虑,可以设置 PG(Placement Group)数量。默认情况下,会创建 replicated 类型的存储池,也就是会存多份,类似 RAID1。也可以设置成 erasure 类型的存储池,类似 RAID5。
每个 Placement Group 里的数据会保存在同一组 OSD 中。数据通过 hash,会分布在不同的 PG 里。
列举所有的存储池:
查看存储池的使用量:
存储池的 IO 状态:
对存储池做快照:
PG 是数据存放的组,每个对象都会放到一个 PG 里面,而 PG 会决定它保存到哪些 OSD 上(具体哪些 OSD 是由 CRUSH 决定的)。PG 数量只有一个的话,那么一个 pool 的所有数据都会存放在某几个 OSD 中,一旦这几个 OSD 都不工作了,那么整个 pool 的数据都不能访问了。PG 增多了以后,就会分布到不同的 OSD 上,并且各个 OSD 的占用也会比较均匀。
查看 PG 状态:
PG 数量可以让集群自动调整:
设置 autoscale 目标为每个 OSD 平均 100 个 PG:
全局 autoscale 开关:
# Enable
ceph osd pool unset noautoscale
# Disable
ceph osd pool set unautoscale
# Read
ceph osd pool get noautoscale
查看 autoscale 状态:
如果没有显示,说明 autoscale 没有工作,可能的原因是,部分 pool 采用了指定 osd class 的 crush rule,例如指定了 hdd 盘,但是也有部分 pool 没有指定盘的类型,例如默认的 replicated_rule。这时候,把这些盘也设置成一个指定 osd class 的 crush rule 即可。
RBD 把 Ceph 暴露为块设备。
初始化 Pool 用于 RBD:
为了安全性考虑,一般会为 RBD 用户创建单独的用户:
ceph auth get-or-create client.abc mon 'profile rbd' osd 'profile rbd pool=xxx' mgr 'profile rbd pool=xxx'
创建 RBD 镜像:
表示在 Pool xxx 上面创建了一个名字为 yyy 大小为 1024MB 的镜像。
列举 Pool 里的镜像:
默认的 Pool 名字是 rbd
。
查看镜像信息:
修改镜像的容量:
在其他机器挂载 RBD 的时候,首先要修改 /etc/ceph
下配置,确认有用户,密钥和 MON 的地址。
然后,用 rbd 挂载设备:
以用户 abc 的身份挂载 Pool xxx 下面的 yyy 镜像。
这时候就可以在 /dev/rbd*
或者 /dev/rbd/
下面看到设备文件了。
显示已经挂载的设备:
如果配置了编排器(Orchestrator),可以直接用命令:
创建一个名为xxx
的 CephFS。
也可以手动创建:
ceph osd pool create xxx_data0
ceph osd pool create xxx_metadata
ceph fs new xxx xxx_metadata xxx_data0
这样就创建了两个 pool,分别用于存储元数据和文件数据。一个 CephFS 需要一个 pool 保存元数据,若干个 pool 保存文件数据。
创建了 CephFS 以后,相应的 MDS 也会启动。
查看 MDS 状态:
在挂载 CephFS 之前,首先要配置客户端。
在集群里运行 ceph config generate-minimal-conf
,它会生成一个配置文件:
$ ceph config generate-minimal-conf
# minimal ceph.conf for <fsid>
[global]
fsid = <fsid>
mon_host = [v2:x.x.x.x:3300/0,v1:x.x.x.x:6789/0]
把内容复制到客户端的 /etc/ceph/ceph.conf
。这样客户端就能找到集群的 MON 地址和 FSID。
接着,我们在集群上给客户端创建一个用户:
创建一个用户 abc,对 CephFS xxx 有读写的权限。把输出保存到客户端的 /etc/ceph/ceph.client.abc.keyring
即可。
挂载:
mount -t ceph abc@.xxx=/ MOUNTPOINT
# or
mount -t ceph abc@<fsid>.xxx=/ MOUNTPOINT
# or
mount -t ceph abc@<fsid>.xxx=/ -o mon_addr=x.x.x.x:6789,secret=REDACTED MOUNTPOINT
#or
mount -t ceph abc@.xxx=/ -o mon_addr=x.x.x.x:6789/y.y.y.y:6789,secretfile=/etc/ceph/xxx.secret MOUNTPOINT
# or
mount -t ceph -o name=client.abc,secret=REDACTED,mds_namespace=xxx MON_IP:/ MOUNTPOINT
以用户 client.abc
的身份登录,挂载 CepFS xxx
下面的 /
目录到 MOUNTPOINT
。它会读取 /etc/ceph
下面的配置,如果已经 ceph.conf
写了,命令行里就可以不写。
fsid 指的不是 CephFS 的 ID,实际上是集群的 ID:ceph fsid
。
CephFS 可以对目录进行限额:
setfattr -n ceph.quota.max_bytes -v LIMIT PATH
setfattr -n ceph.quota.max_files -v LIMIT PATH
getfattr -n ceph.quota.max_bytes PATH
getfattr -n ceph.quota.max_files PATH
限制目录大小和文件数量。LIMIT 是 0 的时候表示没有限制。
可以把 CephFS 或者 RGW 通过 NFS 的方式共享出去。
启动 NFS 服务:
在主机上运行 NFS 服务器,NFS 集群的名字叫做 xxx。
查看 NFS 集群信息:
列举所有 NFS 集群:
NFS 导出 CephFS:
ceph nfs export create cephfs --cluster-id xxx --pseudo-path /a/b/c --fsname some-cephfs-name [--path=/d/e/f] [--client_addr y.y.y.y]
这样就导出了 CephFS 内的一个目录,客户端可以通过 NFS 挂载 /a/b/c 路径(pseudo path)来访问。可以设置客户端的 IP 访问权限。
这样在客户端就可以 mount:
RGW 提供了 S3 或者 OpenStack Swift 兼容的对象存储 API。
TODO
由于 Ceph 需要运行多个 daemon,并且都在不同的容器中运行,所以一般会跑一个系统级的编排器,用于新增和管理这些容器。
查看当前编排器:
比较常见的就是 cephadm,安装的时候如果用了 cephadm,那么编排器也是它。
被编排的服务:
被编排的容器:
被编排的主机:
首先,复制 /etc/ceph/ceph.pub
到新机器的 /root/.ssh/authorized_keys
中
接着,添加机器到编排器中:
导出编排器配置:
如果想让一些 daemon 只运行在部分主机上,可以修改:
然后应用:
添加监控相关的服务:
ceph orch apply node-exporter
ceph orch apply alertmanager
ceph orch apply prometheus
ceph orch apply grafana
ceph orch ps
然后就可以访问 Grafana 看到集群的状态。
使用容器编排器来升级:
ceph orch upgrade start --ceph-version x.x.x
ceph orch upgrade start --image quay.io/ceph/ceph:vx.x.x
如果 docker hub 上找不到 image,就从 quay.io 拉取。
查看升级状态:
查看 cephadm 日志:
把应用迁移到 k3s 中,然后用了 traefik 作为 Ingress Controller,发现无法获得真实的用户 IP 地址,而是 cni 内部的地址。搜索了一番,找到了靠谱的解决方案:
Traefik Kubernetes Ingress and X-Forwarded-Headers
具体来说,需要给 traefik 传额外的参数,方法是在 k3s 的配置目录下,添加一个 HelmChartConfig:
# edit /var/lib/rancher/k3s/server/manifests/traefik-config.yaml
# content:
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
valuesContent: |-
additionalArguments:
- "--entryPoints.web.proxyProtocol.insecure"
- "--entryPoints.web.forwardedHeaders.insecure"
这样相当于让 traefik 信任前一级代理传过来的这些头部。更精细的话,还可以设置信任的 IP 地址范围,不过如果 traefik 不会直接暴露出去,就不用考虑这个问题了。
$ esxcli software vib install -d $PWD/Net-Community-Driver_1.2.0.0-1vmw.700.1.0.15843807_18028830.zip
/vmfs/volumes/
里找到更新文件esxcli software sources profile list -d <zip>
esxcli software profile update -p <profile> -d <zip>
ref: Upgrade or Update a Host with Image Profiles
如果 CPU 比较旧,可能会有警告:Updated Plan for CPU Support Discontinuation In Future Major vSphere Releases,按照信息添加参数忽略即可,ESXi 7.0 系列都是支持的,如果之后出了新的版本可能不支持。
$ esxcli network firewall ruleset set -e true -r httpClient
# find profile name
$ esxcli software sources profile list -d https://hostupdate.vmware.com/software/VUM/PRODUCTION/main/vmw-depot-index.xml
# upgrade to 7.0u3 for example
$ esxcli software profile update -p ESXi-7.0U3-18644231-standard -d https://hostupdate.vmware.com/software/VUM/PRODUCTION/main/vmw-depot-index.xml
ref: Update Standalone ESXi Host
目前 OEM 版本还没找到在线升级方法,需要下载 zip 然后按照离线升级方法安装。
列出所有防火墙规则:
允许出站 SSH:
关闭出站 SSH:
参考:Solution: ESXi Installation with USB NIC only fails at 81%
发现以下博客有很多关于 ESXi 的内容:
参考 ESXi/vCSA Release Notes:
/storage/log
满,原因是访问 vmware 网站失败打印的日志太大:/storage/log/vmware/analytics/analytics-runtime.log*
;解决方法:vmon-cli -r analytics
重启服务,然后删掉旧的日志。/storage/log
满,原因是 tomcat 日志太大。XXX Service Health Alarm
:尝试重启对应服务,比如 vmon-cli -r perfcharts
对应 Performance Charts
,vmon-cli -r vapi-endpoint
对应 VMWare vAPI Endpoint
查看更新状态:cat /storage/core/software-update/stage_operation
;更新文件下载路径:/storage/updatemgr/software-update*/stage
。有一个包特别大:wcpovf
需要两个多 G。
CLI 更新方法:https://earlruby.org/2021/01/upgrading-vcenter-7-via-the-command-line/
首先,unregister 原来的 VM,然后把文件移动到新的路径下。对于 Thin Provisioned Disk,需要特殊处理,否则直接复制的话,会变成 Thick Provisioned Disk,正确方法是采用 vmkfstool
:
需要注意的是,这里的路径用的是不带 -flat
的 vmdk,因为这个文件记录了 metadata,而 -flat.vmdk
保存了实际的数据。可以用 du
命令看实际的硬盘占用,从而确认它确实是 Thin Provisioned。
如果已经在 Web UI 上复制了,你会发现无法停止复制,解决办法是:
这样就会重启 Web UI,不过等它恢复需要很长的时间,还要删掉 cookie。
用 k8s 也有一段时间了,之前遇到过 iptables 等出现问题,导致 k8s 节点间网络出现问题,于是想研究一下 k8s 的网络工作原理。
首先研究一下 Docker 网络连接是如何实现的。Docker 首先会创建一个 bridge,名为 bridge0:
$ ip a show docker0
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:c4:87:73:bf 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:c4ff:fe87:73bf/64 scope link
valid_lft forever preferred_lft forever
默认情况下,每个容器都会有单独的一个 netns,然后创建一对 veth pair,一端留在 global netns,另一端放到容器中。在 global netns 中的 veth 端口会加入到 docker0 中:
$ ip a show dev veth3db9316
21: veth3db9316@if20: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether e2:49:a6:2d:5a:bd brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::e049:a6ff:fe2d:5abd/64 scope link
valid_lft forever preferred_lft forever
$ brctl show docker0
bridge name bridge id STP enabled interfaces
docker0 8000.0242c48773bf no veth3db9316
容器中的网络,在 veth 上 docker 会分配并配置一个地址(比如 172.17.0.2),然后设置默认路由 via 172.17.0.1。一方面,可以通过默认路由到 172.17.0.1 再通过 iptables NAT 访问外面的网络:
$ iptables-save -t nat
# Generated by xtables-save v1.8.2 on Sat Sep 18 10:44:49 2021
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:DOCKER - [0:0]
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A DOCKER -i docker0 -j RETURN
COMMIT
# Completed on Sat Sep 18 10:44:49 2021
另一方面,因为连接不同容器的 veth 在同一个 bridge 下面,所以不同容器的可以认为在同一个二层网络中,自然可以互相访问。
在 k8s 中,所有的 pod 都希望可以通过 IP 地址互联。一个思路是把各个节点上的 pod 通过类似 docker 的方法实现,即每个 netns 通过 veth 连接到一个 bridge 上,然后再想办法去路由在其它节点上的 pod。
因为我用 k3s 搭建 k8s 集群,它用的 cni 是 flannel。flannel 采用的是 vxlan 的方式来实现节点间的网络通信。
首先还是看看节点内的 pod 如何组网。
5: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default qlen 1000
link/ether 6a:4f:ff:8b:b1:b3 brd ff:ff:ff:ff:ff:ff
inet 10.42.0.1/24 brd 10.42.0.255 scope global cni0
valid_lft forever preferred_lft forever
inet6 fe80::7cf6:57ff:fed7:c49b/64 scope link
valid_lft forever preferred_lft forever
6: vethc47d6140@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
link/ether da:19:f8:48:f6:49 brd ff:ff:ff:ff:ff:ff link-netns cni-9d2a5120-16a3-453e-bf64-c4006c06c93b
inet6 fe80::d819:f8ff:fe48:f649/64 scope link
valid_lft forever preferred_lft forever
首先,flannel 给每个节点分配了一个 /24 的网段,比如第一个节点是 10.42.0.0/24,第二个是 10.42.1.0/24,依次类推。然后,节点内的 pod 就从这个网段里分配地址,比如 10.42.0.50/24,它的默认网关是 10.42.0.1。这些 veth 都会加入到 cni0 的 bridge 中。这一部分原理和 docker 是一样的,只不过名字不同了。也有相应的 iptables 规则:
$ iptables-save | grep MASQUERADE
-A POSTROUTING -s 10.42.0.0/16 ! -d 224.0.0.0/4 -j MASQUERADE --random-fully
-A POSTROUTING ! -s 10.42.0.0/16 -d 10.42.0.0/16 -j MASQUERADE --random-fully
那么,节点间网络如何实现呢?假如,我们要从第一个节点 pod 10.42.0.50/24 访问第二个节点的 pod 10.42.1.51/24,首先,pod 根据默认路由会发给 10.42.0.1/24,到达第一个节点的 cni0,然后查路由表:
$ ip r
10.42.0.0/24 dev cni0 proto kernel scope link src 10.42.0.1
10.42.1.0/24 via 10.42.1.0 dev flannel.1 onlink
可以看到,它会匹配 10.42.1.0/24 via 10.42.1.0 dev flannel.1 的路由。flannel.1 是一个 vxlan 的 interface:
$ ip a show flannel.1
4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default
link/ether b6:2f:39:4a:02:c0 brd ff:ff:ff:ff:ff:ff
inet 10.42.0.0/32 scope global flannel.1
valid_lft forever preferred_lft forever
inet6 fe80::b42f:39ff:fe4a:2c0/64 scope link
valid_lft forever preferred_lft forever
当这个 interface 接收到一个 packet 的时候,会查询 fdb:
这个 fdb 中包括了 (MAC 地址,IP 地址) 的 tuple。当 flannel.1 收到一个 Ethernet Frame 的时候,如果目的地址匹配这里的 MAC 地址,就会直接把 Eth Frame 封装到 UDP 里面发给目的 IP 地址;否则,就会在这个表里面 broadcast。这样,第二个节点就会收到 packet 并且转给实际的 pod。
总结一下 k8s 的网络互联的实现方法:节点内通过 bridge 实现,把链接各个 netns 的 veth 桥接起来;节点间划分为多个子网,子网间通过 flannel 的网关进行路由,flannel 网关间通过 vxlan 进行互联。
前段时间用 rook 搭建了一个 k8s 内部的 ceph 集群,但是使用过程中遇到了一些稳定性问题,所以想要用 cephadm 重建一个 ceph 集群。
重建的时候,我首先用 cephadm 搭建了一个 ceph 集群,再把原来的 MON 数据导入,再恢复各个 OSD。理论上,可能有更优雅的办法,但我还是慢慢通过比较复杂的办法解决了。
首先,配置 TUNA 源,在各个节点上安装 docker-ce
和 cephadm
。接着,在主节点上 bootstrap:
此时,在主节点上会运行最基础的 ceph 集群,不过此时还没有任何数据。寻找 ceph 分区,会发现因为 FSID 不匹配而无法导入。所以,首先要恢复 MON 数据。
参考文档:cephadm install。
首先,关掉 rook ceph 集群,找到留存下来的 MON 数据目录,默认路径是 /var/lib/rook
下的 mon-[a-z]
目录,找到最新的一个即可。我把目录下的路径覆盖到 cephadm 生成的 MON 目录下,然后跑起来,发现有几个问题:
/var/lib/ceph/$FSID
下第一个问题的解决办法就是临时用 MON 目录下的 keyring 进行认证,再创建一个新的 client.admin 认证。第二个问题的解决办法就是将遇到的各种 cephadm 生成的 FSID 替换为 MON 中的 FSID,包括目录名、各个目录下 unit.run 中的路径和 systemd unit 的名称。
进行一系列替换以后,原来的 MON 已经起来了,可以看到原来保留的各个 pool 和 cephfs 信息。
接下来,由于 MON 中保存的数据更新了,所以要重新生成 cephadm 的 SSH 密钥。将 SSH 密钥复制到各节点后,再用 cephadm 的 orch 功能部署到其他节点上。此时 FSID 都已经是 MON 中的 FSID,不需要替换。此时可以在 ceph orch ps
命令中看到在各个节点上运行的程序。接下来,还需要恢复各个 OSD。
为了从 ceph 分区从导出 OSD 的配置文件,需要用 ceph-volume
工具。这个工具会生成一个 /var/lib/ceph/osd-ID
目录,在 cephadm 的概念里属于 legacy,因此我们首先要把路径 mount 到 shell 里面:
接着,生成 osd 目录配置:
然后,可以看到创建了对应的 osd 路径,再用 cephadm 进行转换:
这样就可以用 cephadm 管理了。
配置好外部 ceph 集群后,还需要配置 k8s rook。
参考 https://rook.github.io/docs/rook/v1.8/ceph-cluster-crd.html#external-cluster,大概有这么几步:
最近有一台机器的盘出现了报警,需要换掉,然后重建 RAID5 阵列。iDRAC 出现报错:
首先,因为系统是 VMware ESXi 6.7,所以在DELL 官网下载对应的文件。按照里面的 README 安装 vib:
如果要升级系统,需要先卸载 vib:esxcli software vib remove -n vmware-perccli
,因为升级的时候会发现缺少新版系统的 perccli,建议先卸载,升级后再安装新的。
需要注意的是,如果复制上去 Linux 版本的 PERCCli,虽然也可以运行,但是找不到控制器。安装好以后,就可以运行 /opt/lsi/perccli/perccli
。接着,运行 perccli show all
,可以看到类似下面的信息:
$ perccli show all
--------------------------------------------------------------------------------
EID:Slt DID State DG Size Intf Med SED PI SeSz Model Sp Type
--------------------------------------------------------------------------------
32:2 2 Failed 1 3.637 TB SATA HDD N N 512B ST4000NM0033-9ZM170 U -
32:4 4 UGood F 3.637 TB SATA HDD N N 512B ST4000NM0033-9ZM170 U -
--------------------------------------------------------------------------------
其中 E32S2 是 Failed 的盘,属于 Disk Group 1;E32S4 是新插入的盘,准备替换掉 E32S2,目前不属于任何的 Disk Group。查看一下 Disk Group:perccli /c0/dall show
$ perccli /c0/dall show
-----------------------------------------------------------------------------
DG Arr Row EID:Slot DID Type State BT Size PDC PI SED DS3 FSpace TR
-----------------------------------------------------------------------------
1 - - - - RAID5 Dgrd N 7.276 TB dflt N N dflt N N
1 0 - - - RAID5 Dgrd N 7.276 TB dflt N N dflt N N
1 0 0 32:1 1 DRIVE Onln N 3.637 TB dflt N N dflt - N
1 0 1 32:2 2 DRIVE Failed N 3.637 TB dflt N N dflt - N
1 0 2 32:3 3 DRIVE Onln N 3.637 TB dflt N N dflt - N
可以看到 DG1 处于 Degraded 状态,然后 E32S4 处于 Failed 状态。参考了一下 PERCCli 文档,它告诉我们要这么做:
perccli /cx[/ex]/sx set offline
perccli /cx[/ex]/sx set missing
perccli /cx /dall show
perccli /cx[/ex]/sx insert dg=a array=b row=c
perccli /cx[/ex]/sx start rebuild
具体到我们这个情景,就是把 E32S2 设为 offline,然后用 E32S4 来替换它:
perccli /c0/e32/s2 set offline
perccli /c0/e32/s2 set missing
perccli /cx /dall show
perccli /cx/e32/s4 insert dg=1 array=0 row=2
perccli /cx/e32/s4 start rebuild
完成以后的状态:
TOPOLOGY :
========
---------------------------------------------------------------------------
DG Arr Row EID:Slot DID Type State BT Size PDC PI SED DS3 FSpace TR
---------------------------------------------------------------------------
1 - - - - RAID5 Dgrd N 7.276 TB dflt N N dflt N N
1 0 - - - RAID5 Dgrd N 7.276 TB dflt N N dflt N N
1 0 0 32:1 1 DRIVE Onln N 3.637 TB dflt N N dflt - N
1 0 1 32:4 4 DRIVE Rbld Y 3.637 TB dflt N N dflt - N
1 0 2 32:3 3 DRIVE Onln N 3.637 TB dflt N N dflt - N
---------------------------------------------------------------------------
可以看到 E32S4 替换了原来 E32S2 的位置,并且开始重建。查看重建进度:
$ perccli /c0/32/s4 show rebuild
-----------------------------------------------------
Drive-ID Progress% Status Estimated Time Left
-----------------------------------------------------
/c0/e32/s4 3 In progress -
-----------------------------------------------------
$ perccli show all
Need Attention :
==============
Controller 0 :
============
-------------------------------------------------------------------------------
EID:Slt DID State DG Size Intf Med SED PI SeSz Model Sp Type
-------------------------------------------------------------------------------
32:4 4 Rbld 1 3.637 TB SATA HDD N N 512B ST4000NM0033-9ZM170 U -
-------------------------------------------------------------------------------
然后,查看一下出错的盘:
$ perccli /c0/e32/s2 show all
Drive /c0/e32/s2 State :
======================
Shield Counter = 0
Media Error Count = 0
Other Error Count = 6
Drive Temperature = 36C (96.80 F)
Predictive Failure Count = 0
S.M.A.R.T alert flagged by drive = No
果然有错误,但是也看不到更多信息了。
坏块统计:
$ perccli /c0 show badblocks
Detailed Status :
===============
-------------------------------------------------------------
Ctrl Status Ctrl_Prop Value ErrMsg ErrCd
-------------------------------------------------------------
0 Failed Bad Block Count - BadBlockCount failed 2
-------------------------------------------------------------
经过检查以后,发现 E32S2 盘的 SMART 并没有报告什么问题,所以也没有把盘取走,而是作为 hot spare 当备用:
$ perccli /c0/e32/s2 add hotsparedrive DG=1
$ perccli /c0/d1 show
TOPOLOGY :
========
---------------------------------------------------------------------------
DG Arr Row EID:Slot DID Type State BT Size PDC PI SED DS3 FSpace TR
---------------------------------------------------------------------------
1 - - - - RAID5 Dgrd N 7.276 TB dflt N N dflt N N
1 0 - - - RAID5 Dgrd N 7.276 TB dflt N N dflt N N
1 0 0 32:1 1 DRIVE Onln N 3.637 TB dflt N N dflt - N
1 0 1 32:4 4 DRIVE Rbld Y 3.637 TB dflt N N dflt - N
1 0 2 32:3 3 DRIVE Onln N 3.637 TB dflt N N dflt - N
1 - - 32:2 2 DRIVE DHS - 3.637 TB - - - - - N
---------------------------------------------------------------------------
DG=Disk Group Index|Arr=Array Index|Row=Row Index|EID=Enclosure Device ID
DID=Device ID|Type=Drive Type|Onln=Online|Rbld=Rebuild|Optl=Optimal|Dgrd=Degraded
Pdgd=Partially degraded|Offln=Offline|BT=Background Task Active
PDC=PD Cache|PI=Protection Info|SED=Self Encrypting Drive|Frgn=Foreign
DS3=Dimmer Switch 3|dflt=Default|Msng=Missing|FSpace=Free Space Present
TR=Transport Ready
这样就可以做后备盘,当别的盘坏的时候,作为备用。
StorCLI:
MegaCLI:
PercCLI:
在维护一个 k8s 集群的时候,一个很常见的需求就是把日志持久化存下来,一方面是方便日后回溯,一方面也是聚合 replicate 出来的同一个服务的日志。
在我目前的需求下,只需要把日志持久下来,还不需要做额外的分析。所以我并没有部署类似 ElasticSearch 的服务来对日志进行索引。
实现主要参考官方的仓库:https://github.com/fluent/fluentd-kubernetes-daemonset。它把一些常用的插件打包到 docker 镜像中,然后提供了一些默认的设置,比如获取 k8s 日志和 pod 日志等等。为了达到我的需求,我希望:
由于 log server 不由 k8s 管理,所以按照官网的方式手动安装:
然后,编辑配置 /etc/td-agent/td-agent.conf
:
<source>
@type forward
@id input_forward
bind x.x.x.x
</source>
<match **>
@type file
path /var/log/fluentd/k8s
compress gzip
<buffer>
timekey 1d
timekey_use_utc true
timekey_wait 10m
</buffer>
</match>
分别设置输入:监听 fluentd forward 协议;输出:设置输出文件,和 buffer 配置。如有需要,可以加鉴权。
接着,按照 https://github.com/fluent/fluentd-kubernetes-daemonset/blob/master/fluentd-daemonset-forward.yaml,我做了一些修改,得到了下面的配置:
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: fluentd
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: fluentd
namespace: kube-system
rules:
- apiGroups:
- ""
resources:
- pods
- namespaces
verbs:
- get
- list
- watch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: fluentd
roleRef:
kind: ClusterRole
name: fluentd
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: fluentd
namespace: kube-system
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd
namespace: kube-system
labels:
k8s-app: fluentd-logging
version: v1
spec:
selector:
matchLabels:
k8s-app: fluentd-logging
version: v1
template:
metadata:
labels:
k8s-app: fluentd-logging
version: v1
spec:
serviceAccount: fluentd
serviceAccountName: fluentd
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- name: fluentd
image: fluent/fluentd-kubernetes-daemonset:v1-debian-forward
env:
- name: FLUENT_FOWARD_HOST
value: "x.x.x.x"
- name: FLUENT_FOWARD_PORT
value: "24224"
- name: FLUENTD_SYSTEMD_CONF
value: "disable"
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
volumeMounts:
- name: config-volume
mountPath: /fluentd/etc/tail_container_parse.conf
subPath: tail_container_parse.conf
- name: varlog
mountPath: /var/log
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
terminationGracePeriodSeconds: 30
volumes:
- name: config-volume
configMap:
name: fluentd-config
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
---
apiVersion: v1
kind: ConfigMap
metadata:
name: fluentd-config
namespace: kube-system
data:
tail_container_parse.conf: |-
<parse>
@type cri
</parse>
和原版有几点细节上的不同:
部署到 k8s 中即可。为了保证日志的准确性,建议各个结点都保持 NTP 的同步。
在 k8s 集群中部署了 gitlab-runner,并且希望在 gitlab ci 构建完成后,把新的 docker image push 到 private repo,然后更新应用。
参考文档:Gitlab CI 与 Kubernetes 的结合,Using Docker to build Docker images。
这一步需要 DinD 来实现在容器中构建容器。为了达到这个目的,首先要在 gitlab-runner 的配置中添加一个 volume 来共享 DinD 的证书路径:
gitlabUrl: REDACTED
rbac:
create: true
runnerRegistrationToken: REDACTED
runners:
config: |
[[runners]]
[runners.kubernetes]
image = "ubuntu:20.04"
privileged = true
[[runners.kubernetes.volumes.empty_dir]]
name = "docker-certs"
mount_path = "/certs/client"
medium = "Memory"
privileged: true
注意两点:1. privileged 2. 多出来的 volume
用 helm 部署 gitlab runner 之后,按照下面的方式配置 gitlab-ci:
image: docker:19.03.12
variables:
DOCKER_HOST: tcp://docker:2376
#
# The 'docker' hostname is the alias of the service container as described at
# https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#accessing-the-services.
# If you're using GitLab Runner 12.7 or earlier with the Kubernetes executor and Kubernetes 1.6 or earlier,
# the variable must be set to tcp://localhost:2376 because of how the
# Kubernetes executor connects services to the job container
# DOCKER_HOST: tcp://localhost:2376
#
# Specify to Docker where to create the certificates, Docker will
# create them automatically on boot, and will create
# `/certs/client` that will be shared between the service and job
# container, thanks to volume mount from config.toml
DOCKER_TLS_CERTDIR: "/certs"
# These are usually specified by the entrypoint, however the
# Kubernetes executor doesn't run entrypoints
# https://gitlab.com/gitlab-org/gitlab-runner/-/issues/4125
DOCKER_TLS_VERIFY: 1
DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client"
DOCKER_DAEMON_OPTIONS: "--insecure-registry=${REGISTRY}"
services:
- name: docker:19.03.12-dind
entrypoint: ["sh", "-c", "dockerd-entrypoint.sh $DOCKER_DAEMON_OPTIONS"]
before_script:
# Wait until client certs are generated
# https://gitlab.com/gitlab-org/gitlab-runner/-/issues/27384
- until docker info; do sleep 1; done
- echo "$REGISTRY_PASS" | docker login $REGISTRY --username $REGISTRY_USER --password-stdin
build:
stage: build
script: ./build.sh
这里有很多细节,包括 DinD 的访问方式,等待 client cert,设置 docker 的 insecure registry 和 login 等等。经过 @CircuitCoder 的不断摸索,终于写出了可以用的配置。
如此配置以后,就可以在 gitlab ci 的构建脚本里用 docker 来 build 并且 push 到自己的 registry 了。为了防止泄露密钥,建议把这些变量放到 gitlab ci 设置的 secrets 中。
为了让 k8s 重启一个 deployment,一般的做法是:
我们希望 gitlab ci 在 build 之后,去执行这一个命令,但又不希望提供太多的权限给 gitlab。所以,我们创建 Service Account 并设置最小权限:
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: gitlab
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: gitlab-test
namespace: test
rules:
- verbs:
- get
- patch
apiGroups:
- 'apps'
resources:
- 'deployments'
resourceNames:
- 'test-deployment'
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: gitlab
namespace: test
subjects:
- kind: ServiceAccount
name: gitlab
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: gitlab-test
要特别注意这几个配置的 namespace 的对应关系:Role 和 RoleBinding 需要放在同一个 ns 下。
接着,到 GitLab 的 Operations->Kubernetes 创建 cluster,把 service account 的 token 和 ca.crt 从 secret 里找到并贴到网页上。GitLab 会按照 Environment scope 匹配到 environment,如果某个 stage 的 environment 匹配上了,就会把 kube credentials 配置好。修改 gitlab-ci.yml:
deploy:
stage: deploy
image: bitnami/kubectl:1.20
environment:
name: production
only:
- master
script:
- kubectl -n test rollout restart deployment/test
这样就完成配置了。
为了方便集群的使用,想在 k8s 集群里部署一个 ceph 集群。
Ceph 有这些组成部分:
我们采用的是 rook 来部署 ceph 集群。
参考文档:https://rook.github.io/docs/rook/v1.5/ceph-examples.html
首先,克隆 rook 的仓库。建议选择一个 release 版本。
接着,运行下面的命令:
sudo apt install -y lvm2
# required
kubectl apply -f rook/cluster/examples/kubernetes/ceph/crds.yaml
kubectl apply -f rook/cluster/examples/kubernetes/ceph/common.yaml
kubectl apply -f rook/cluster/examples/kubernetes/ceph/operator.yaml
# debugging only
kubectl apply -f rook/cluster/examples/kubernetes/ceph/toolbox.yaml
kubectl apply -f rook/cluster/examples/kubernetes/ceph/direct-mount.yaml
# CephFS
kubectl apply -f rook/cluster/examples/kubernetes/ceph/filesystem.yaml
kubectl apply -f rook/cluster/examples/kubernetes/ceph/csi/cephfs/storageclass.yaml
前面三个 yaml 是必须的,toolbox 是用来查看 ceph 状态的,direct mount 是用来 mount cephfs 的,后两个是为了用 cephfs 的。
接着,按照自己的需求编辑 rook/cluster/exmaples/kuberenetes/ceph/cluster.yaml
然后应用。此时你的集群应该就已经起来了。
然后,可以进 toolbox 查看 ceph 状态:
# get volume path of pvc
kubectl get pv -o custom-columns=NAME:.metadata.name,NAMSEPACE:.spec.claimRef.namespace,CLAIM:.spec.claimRef.name,PATH:.spec.csi.volumeAttributes.subvolumeName
kubectl -n rook-ceph exec -it deploy/rook-direct-mount -- bash
# in the pod
mkdir /tmp/registry
mon_endpoints=$(grep mon_host /etc/ceph/ceph.conf | awk '{print $3}')
my_secret=$(grep key /etc/ceph/keyring | awk '{print $3}')
mount -t ceph -o mds_namespace=myfs,name=admin,secret=$my_secret $mon_endpoints:/ /tmp/registry
df -h
cd /tmp/registry/volumes/csi/PATH
最近需要部署一个 k8s 集群,觉得之前配置 kubeadm 太繁琐了,想要找一个简单的。比较了一下 k0s 和 k3s,最后选择了 k3s。
k3s 的好处就是配置十分简单:https://rancher.com/docs/k3s/latest/en/quick-start/。不需要装 docker,也不需要装 kubeadm。
curl -sfL https://get.k3s.io | sh -
cat /var/lib/rancher/k3s/server/node-token
curl -sfL https://get.k3s.io | K3S_URL=https://myserver:6443 K3S_TOKEN=mynodetoken sh -
然后就搞定了。从第一个 node 的 /etc/rancher/k3s/k3s.yaml
获取 kubectl
配置。
最近接触了 Cisco,DELL,Huawei,H3C,Ruijie 的网络设备,发现配置方式各有不同,故记录一下各个厂家的命令。
测试型号:S5320
<HUAWEI>save
The current configuration will be written to flash:/vrpcfg.zip.
Are you sure to continue?[Y/N]y
Now saving the current configuration to the slot 0....
Save the configuration successfully.
[HUAWEI]snmp-agent sys-info version all
Warning: This command may cause confliction in netconf status. Continue? [Y/N]:y
Warning: SNMPv1/SNMPv2c is not secure, and it is recommended to use SNMPv3.
[HUAWEI]snmp-agent community read [COMMUNITY NAME]
Warning: This command may cause confliction in netconf status. Continue? [Y/N]:y
默认情况下 SNMP 会缺少一些标准的 MIB(比如 LLDP),可以打开 iso view:
[HUAWEI]snmp-agent mib-view included iso-view iso
Warning: This command may cause confliction in netconf status. Continue? [Y/N]:y
[HUAWEI]snmp-agent community read [COMMUNITY NAME] mib-view iso-view
[HUAWEI-XGigabitEthernet0/0/1]eth-trunk 1
[HUAWEI-XGigabitEthernet0/0/2]eth-trunk 1
[HUAWEI]interface Eth-Trunk 1
[HUAWEI-Eth-Trunk1]mode lacp
测试型号:N3048
console#copy running-config startup-config
This operation may take few minutes.
Management interfaces will not be available during this time.
Are you sure you want to save? (y/n) y
Configuration Saved!
console(config)#interface Gi1/0/1
console(config-if-Gi1/0/1)#switchport mode trunk
console(config-if-Gi1/0/1)#switchport trunk allowed vlan xxx,xxx-xxx
console(config)#interface Gi1/0/1
console(config-if-Gi1/0/1)#switchport mode access
console(config-if-Gi1/0/1)#switchport access vlan xxx
console(config)#crypto key generate rsa
console(config)#crypto key generate dsa
console(config)#ip ssh server
[switch]save
The current configuration will be written to the device. Are you sure? [Y/N]:y
Please input the file name(*.cfg)[flash:/startup.cfg]
(To leave the existing filename unchanged, press the enter key):y
The file name is invalid(does not end with .cfg).
switch (config interface ethernet 1/1) # shutdown
switch (config interface ethernet 1/1) # module-type qsfp-split-4
switch (config interface ethernet 1/1) # channel-group 1 mode active
switch (config interface ethernet 1/2) # channel-group 1 mode active
模式可以选择:active(LACP)/passive(LACP)/on(Static)
# config terminal
(config)# interface ethernet 1/1
(config-if)# switchport mode trunk
(config-if)# switchport trunk allowed vlan 12-34
之前在另一篇文章里提到过 vCSA 的安装,这次又在另一台机器上重新做了一遍,特此记录一下。
首先在官网上下载 ESXi+VCSA 7.0 ,应该得到两个文件:
首先安装 ESXi,用 UNetBootin 制作 ESXi 的安装光盘。注意不能用 dd,因为它是 CDFS 格式的,不能直接 boot。启动以后,按照界面要求,一路安装即可。
接着,就可以用网页访问 ESXi 进行配置。比如安装一些 Linux 发行版,然后在 Linux 虚拟机里面 mount 上面的 VCSA 的 iso:
接着,复制并修改 /mnt/vcsa-cli-installer/templates/install/embedded_vCSA_on_ESi.json
,按照代码注释进行修改。需要注意几点:
接着,进行安装:
慢慢等待它安装成功即可。
安装完成后,进入 vCSA,新建一个 Datacenter,然后选择新建的 Datacenter,选择 Add host,输入 ESXi 的地址和用户密码信息即可。
需要给 rpi 配置一个 pxe 的最小环境,在上一篇博文了提到可以用 alpine,但发现有一些不好用的地方,所以试了试 buildroot。
见“在 Rpi4 上运行 Alpine Linux”文章。
下载 buildroot:
> wget https://buildroot.org/downloads/buildroot-2020.08.tar.gz
> unar buildroot-2020.08.tar.gz
> cd buildroot-2020.08
> make raspberrypi4_64_defconfig
然后运行 make menuconfig
,在 Filesystem images
中打开 initramfs,并设置 cpio 压缩为 gz。然后直接编译:
> make -j4
$ ls -al target/images
bcm2711-rpi-4-b.dtb* boot.vfat Image rootfs.cpio rootfs.cpio.gz rootfs.ext2 rootfs.ext4@ rpi-firmware/ sdcard.img
接着,在一个单独的目录里,把这些文件整理一下
> cd ~/rpi-buildroot
> cp -r ~/buildroot-2020.08/output/images/rpi-firmware/* .
> cp ~/buildroot-2020.08/output/images/bcm2711-rpi-4-b.dtb .
> cp ~/buildroot-2020.08/output/images/Image .
> cp ~/buildroot-2020.08/output/images/rootfs.cpio.gz .
> # edit cmdline.txt: remove root= and rootwait
> # edit config.txt: uncomment initramfs rootfs.cpio.gz line
# ls
bcm2711-rpi-4-b.dtb* cmdline.txt config.txt fixup.dat Image overlays/ rootfs.cpio.gz start.elf
最后开启 TFTP 服务器即可:
连接树莓派的串口,用 115200 Baudrate 打开,可以看到启动信息:
PM_RSTS: 0x00001000
RPi: BOOTLOADER release VERSION:a5e1b95f DATE: Apr 16 2020 TIME: 18:11:29 BOOTMODE: 0x00000006 part: 0 BUILD_TIMESTAMP=1587057086 0xa049cc2f 0x00c03111
uSD voltage 3.3V
...
Welcome to Buildroot
buildroot login: root
#
默认用户是 root,没有密码。
需要给 rpi 配置一个 pxe 的最小环境,然后看到 alpine 有 rpi 的支持,所以尝试给 rpi4 配置 alpine。
第一步是设置 rpi4 的启动模式,打开 BOOT UART 并且打开 网络启动:
> cd /lib/firmware/raspberrypi/bootloader/critical
> rpi-eeprom-config pieeprom-2021-04-29.bin > config.txt
$ cat config.txt
[all]
BOOT_UART=1
WAKE_ON_GPIO=1
POWER_OFF_ON_HALT=0
DHCP_TIMEOUT=45000
DHCP_REQ_TIMEOUT=4000
TFTP_FILE_TIMEOUT=30000
TFTP_IP=
TFTP_PREFIX=0
BOOT_ORDER=0x1
SD_BOOT_MAX_RETRIES=3
NET_BOOT_MAX_RETRIES=5
[none]
FREEZE_VERSION=0
> sed 's/BOOT_UART=0/BOOT_UART=1/;s/BOOT_ORDER=0x1/BOOR_ORDER=0x12/' config.txt > config-pxe.txt
> rpi-eeprom-config --out pieeprom-2021-04-29-pxe.bin --config config-pxe.txt pieeprom-2021-04-29.bin
> rpi-eeprom-update -d -f pieeprom-2021-04-29-pxe.bin
> reboot
重启以后,可以用 vcgencmd bootloader_config
查看当前的启动配置,看是否正确地更新了启动配置。比较重要的是 BOOT_ORDER,0x12
表示先尝试网络启动,再尝试 SD 卡启动。
第二步,需要配置路由器,以 OpenWrt 为例:
> uci add_list dhcp.lan.dhcp_option="66,ip_address_of_tftp_server"
> uci commit dhcp
> /etc/init.d/dnsmasq restart
$ cat /etc/config/dhcp
...
config dhcp 'lan'
...
list dhcp_option '66,ip_address_of_tftp_server'
...
这样就配置完毕了。如果是 isc-dhcp-server,修改 /etc/dhcp/dhcpd.conf
:
subnet 10.0.1.0 netmask 255.255.255.0 {
range 10.0.1.100 10.0.1.199;
option routers 10.0.1.1;
option tftp-server-name "10.0.1.1";
}
下载 alpine linux 的 rpi boot,解压到指定目录:
> wget http://mirrors.tuna.tsinghua.edu.cn/alpine/v3.12/releases/aarch64/alpine-rpi-3.12.0-aarch64.tar.gz
> unar alpine-rpi-3.12.0-aarch64.tar.gz
> cd alpine-rpi-3.12.0-aarch64
修改 cmdline.txt
,把 console=tty1
改成 console=ttyAMA0,115200
,并且去掉 quiet
;修改 usercfg.txt
为:
接着,启动 TFTP 服务器:
连接树莓派的串口,用 115200 Baudrate 打开,可以看到启动信息:
PM_RSTS: 0x00001000
RPi: BOOTLOADER release VERSION:a5e1b95f DATE: Apr 16 2020 TIME: 18:11:29 BOOTMODE: 0x00000006 part: 0 BUILD_TIMESTAMP=1587057086 0xa049cc2f 0x00c03111
uSD voltage 3.3V
...
initramfs emergency recovery shell launched. Type 'exit' to continue boot
sh: can't access tty; job control turned off
/ #
然后,按照需要自定义 initramfs 即可。解压后,修改文件,然后运行:
把自带的 initramfs 替换掉。
最近遇到了 AWS Certificate Manager 的一些限制,所以只能用 IAM 证书。于是上网找到了通过 certbot 申请 LE 证书,通过 route53 API 验证的方法。
首先配置 aws 的 credential。然后,按照 certbot:
然后,就可以申请证书了:
certbot certonly --dns-route53 --config-dir "./letsencrypt" --work-dir "./letsencrypt" --logs-dir "./letsencrypt" -d example.com --email a@b.com --agree-tos
如果申请成功,在当前目录下可以找到证书。然后上传到 IAM:
aws iam upload-server-certificate --server-certificate-name NameHere \
--certificate-body file://letsencrypt/live/example.com/cert.pem \
--private-key file://letsencrypt/live/example.com/privkey.pem \
--certificate-chain file://letsencrypt/live/example.com/chain.pem \
--path /cloudfront/
如果要用于 cloudfront,才需要最后的路径参数;否则可以去掉。这样就完成了 IAM 证书的上传。
实验了一下在 k8s 中部署 Prometheus,因为它和 k8s 有比较好的集成,很多 App 能在 k8s 里通过 service discovery 被 Prometheus 找到并且抓取数据。实践了一下,其实很简单。
用 helm 进行配置:
这样就可以了,如果已经有 StorageClass(比如腾讯云的话,CBS 和 CFS),它就能自己起来了,然后在 Lens 里面也可以看到各种 metrics 的可视化。
如果是自建的单结点的 k8s 集群,那么还需要自己创造 PV,并且把 PVC 绑定上去。我采用的是 local 类型的 PV:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-volume-1
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/srv/k8s-data-1"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-volume-2
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/srv/k8s-data-2"
这样,结点上的两个路径分别对应两个 PV,然后只要让 PVC 也用 manual 的 StorageClass 就可以了:
把这个文件保存为 values.yaml 然后:
这样就可以了。不过 PVC 不能在线改,可能需要删掉重来。
然后,由于权限问题,还需要在结点上修改一下两个目录的权限:
这样容器内就可以正常访问了。
实验了一下在 k8s 中部署 code-server,并不复杂,和之前几篇博客的配置是类似的:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: code
labels:
app: code
spec:
selector:
matchLabels:
app: code
replicas: 1
template:
metadata:
labels:
app: code
spec:
volumes:
- name: code-volume
persistentVolumeClaim:
claimName: code-pvc
initContainers:
- name: home-init
image: busybox
command: ["sh", "-c", "chown -R 1000:1000 /home/coder"]
volumeMounts:
- mountPath: "/home/coder"
name: code-volume
containers:
- image: codercom/code-server:latest
imagePullPolicy: Always
name: code
volumeMounts:
- mountPath: "/home/coder"
name: code-volume
resources:
limits:
cpu: "0.5"
memory: "500Mi"
ports:
- containerPort: 8080
env:
- name: PASSWORD
value: REDACTED
---
apiVersion: v1
kind: Service
metadata:
name: code
labels:
app: code
spec:
ports:
- port: 8080
selector:
app: code
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: ingress-code
annotations:
kubernetes.io/ingress.class: "nginx"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.org/websocket-services: "code"
spec:
tls:
- hosts:
- example.com
secretName: code-tls
rules:
- host: example.com
http:
paths:
- path: /
backend:
serviceName: code
servicePort: 8080
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: code-pvc
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 1Gi
需要注意的几个点:
实验了一下在 k8s 中部署 CI,在 drone gitlab-ci 和 jenkins 三者中选择了 drone,因为它比较轻量,并且基于 docker,可以用 GitHub 上的仓库,比较方便。
首先,配置 helm:
参考 drone 的文档,编写 drone-values.yml:
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: "nginx"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
hosts:
- host: drone.example.com
paths:
- "/"
tls:
- hosts:
- drone.example.com
secretName: drone-tls
env:
DRONE_SERVER_HOST: drone.example.com
DRONE_SERVER_PROTO: https
DRONE_USER_CREATE: username:YOUR_GITHUB_USERNAME,admin:true
DRONE_USER_FILTER: YOUR_GITHUB_USERNAME
DRONE_RPC_SECRET: REDACTED
DRONE_GITHUB_CLIENT_ID: REDACTED
DRONE_GITHUB_CLIENT_SECRET: REDACTED
需要首先去 GitHub 上配置 OAuth application,具体参考 drone 的文档。然后,生成一个 secret,设置 admin 用户并只允许 admin 用户使用 drone,防止其他人使用。然后应用:
helm install --namespace drone drone drone/drone -f drone-values.yml
# or, to upgrade
helm upgrade --namespace drone drone drone/drone --values drone-values.yml
然后就可以访问上面配好的域名了。遇到了 cert manager 最近的一个 bug,来回折腾几次就好了。
接着配 drone 的 k8s runnner,也是参考 drone 的文档,编写 drone-runner-kube-values.yml:
然后应用:
helm install --namespace drone drone-runner-kube drone/drone-runner-kube -f drone-runner-kube-values.yml
然后就可以去 drone 界面上操作了。
需要注意的是,drone 需要 pv,所以我先在腾讯云里面配置了 CFS 的 storage class,然后它就会自动 provision 一个新的 pv 和 pvc 出来。
接着尝试了一下在 drone 里面构建 docker 镜像并且 push 到 registry 上。以腾讯云为例:
kind: pipeline
type: kubernetes
name: default
steps:
- name: build
image: alpine
commands:
- make
- name: publish
image: plugins/docker
settings:
registry: ccr.ccs.tencentyun.com
repo: ccr.ccs.tencentyun.com/abc/def
tags: ["${DRONE_COMMIT_SHA:0:7}","latest"]
username:
from_secret: docker_username
password:
from_secret: docker_password
然后在网页里配置好 docker username 和 password,它就会自动构建 docker 镜像并且 push 到 registry 上,然后再 rollout 一下 deployment 就能部署最新的 image 了。实际上可以在 drone 里面把部署这一步也完成,但目前还没有去实践。
参考文档:
上一篇博客讲了 nginx ingress 的配置,那自然第一步要配上 https。首先配置 cert-manager:
$ kubectl create namespace cert-manager
$ kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.14.1/cert-manager.crds.yaml
$ helm repo add jetstack https://charts.jetstack.io
$ helm repo update
$ helm install \
cert-manager jetstack/cert-manager \
--namespace cert-manager \
--version v0.14.1
然后,配置 Cluster Issuer,应用以下的 yaml:
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
namespace: cert-manager
spec:
acme:
email: example@example.com
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
然后在 ingress 里面进行配置:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: ingress-example
annotations:
kubernetes.io/ingress.class: "nginx"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- example.com
secretName: example-tls
rules:
- host: example.com
http:
paths:
- path: /
backend:
serviceName: example-service
servicePort: 80
应用以后,用 kubectl describe certificate
查看证书获取进度。成功后,访问改域名的 HTTP,就会自动跳转到 HTTPS,并且提供了正确的证书。
想要在 k8s 里面 host 一个网站,但又不想额外花钱用 LB,想直接用节点的 IP。
首先安装 nginx-ingress:
$ helm repo add nginx-stable https://helm.nginx.com/stable
$ helm repo update
$ helm install ingress-controller nginx-stable/nginx-ingress --set controller.service.type=NodePort --set controller.hostNetwork=true
这里给 ingress controller chart 传了两个参数:第一个指定 service 类型是 NodePort,替代默认的 LoadBalancer;第二个指定 ingress controller 直接在节点上监听,这样就可以用节点的公网 IP 访问了。
然后配一个 ingress:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: ingress-example
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: example.com
http:
paths:
- path: /
backend:
serviceName: example-service
servicePort: 80
然后就可以发现请求被正确路由到 example-service 的 80 端口了。
之前在机器上试验了一下 kubernetes,感觉挺不错的,所以就想把腾讯云上面的机器也交给 kubernetes 管理。找到容器服务,新建集群,选择模板里的标准托管集群就可以了。然后开启下面的公网访问,设置一个比较小的 IP 地址段,按照页面下面的要求合并一下 kube config(因为还有别的 k8s cluster):
$ KUBECONFIG=~/.kube/config:/path/to/new/config kubectl config view --merge --flatten > new_config
$ cp new_config ~/.kube/config
覆盖之前先确认原来的配置还在,然后就可以用 kubectl 切换到新的 context:
$ kubectl config get-contexts
$ kubectl config use-context new-context-here
$ kubectl get node
NAME STATUS ROLES AGE VERSION
172.21.0.17 Ready <none> 75m v1.16.3-tke.3
可以看到我们的 k8s node 已经上线了。我一般习惯先配好 kubernetes-dashboard:
$ kubectl create -f https://raw.githubusercontent.com/cilium/cilium/v1.6/install/kubernetes/quick-install.yaml
$ kubectl proxy &
$ kubectl -n kubernetes-dashboard describe secret (kubectl -n kubernetes-dashboard get secret | grep admin-user | awk '{print \$1}') | tail -n1 | awk '{print \$2}' | pbcopy
然后在浏览器里访问 http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/#/overview?namespace=default 然后把剪贴板里的 token 粘贴进去即可。
默认情况下 kubernetes-dashboard 的权限比较少,可以让它获得更多权限:
$ kubectl edit clusterrole.rbac.authorization.k8s.io/kubernetes-dashboard
# use '*' for ultimate
# use `kubectl get clusterrole.rbac.authorization.k8s.io/cluster-admin -o yaml` to see full permissions
接下来配置 metrics-server。下载 metrics-server 仓库,然后修改镜像地址:
$ wget https://github.com/kubernetes-sigs/metrics-server/archive/v0.3.6.zip
$ unar v0.3.6.zip
$ cd metrics-server-0.3.6/deploy/1.8+
$ vim metrics-server-deployment
# change: k8s.gcr.io/metrics-server-amd64:v0.3.6
# to: registry.cn-hangzhou.aliyuncs.com/google_containers/metrics-server-amd64:v0.3.6
# add line below image: args: ["--kubelet-insecure-tls"]
$ kubectl apply -f .
等一段时间,就可以看到 metrics server 正常运行了。
参考:https://tencentcloudcontainerteam.github.io/tke-handbook/
按照 GitLab 上的教程试着把 gitlab-runner 部署到 k8s 集群上,发现异常地简单,所以简单做个笔记:
编辑 values.yaml
此处的信息按照 "Set up a specific Runner manually" 下面的提示填写。然后用 Helm 进行安装:
$ helm repo add gitlab https://charts.gitlab.io
$ kubectl create namespace gitlab-runner
$ helm install --namespace gitlab-runner gitlab-runner -f values.yaml gitlab/gitlab-runner
然后去 Kubernetes Dashboard 就可以看到部署的情况,回到 GitLab 也可以看到出现了“Runners activated for this project” ,表示配置成功。
参考配置:https://docs.gitlab.com/runner/install/kubernetes.html