记录下如何移除swift文件的头部注释

注意,zsh要在空格处添加反斜杠 \,否则无法生成正确的文件路径

清空模板注释内容(可选)

以下操作可以先备份文件

修改IDETemplateMacros.plist文件,移除注释内容

1
vi ~/Library/Developer/Xcode/UserData/IDETemplateMacros.plist

找到FILEHEADER,将string的内容清空

Swift File

进入Xcode目录

1
cd ~/Library/Developer/Xcode/

添加模板No Comment Swift File.xctemplate,该模板会展示在Xcode的文件目录列表

1
mkdir -p Templates/File Templates/MultiPlatform/Source/No Comment Swift File.xctemplate

进入模板目录

1
cd Templates/File Templates/MultiPlatform/Source/No Comment Swift File.xctemplate

复制原swift file模板数据

1
cp /Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/File Templates/MultiPlatform/Source/Swift File.xctemplate/* ./

修改___FILEBASENAME___.swift,将第一行头部注释部分移除

SwiftUI View

同上,进入Xcode目录

1
cd ~/Library/Developer/Xcode

添加模板No Comment SwiftUI View.xctemplate

1
mkdir -p Templates/File Templates/MultiPlatform/User Interface/No Comment SwiftUI View.xctemplate

进入模板目录

1
cd Templates/File Templates/MultiPlatform/User Interface/No Comment SwiftUI View.xctemplate

复制原swiftui view模板数据

1
cp /Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/File Templates/MultiPlatform/User Interface/SwiftUI View.xctemplate/* ./

修改___FILEBASENAME___.swift,将第一行头部注释部分移除

最后

重启Xcode(不是关闭打开xcode窗口,而是重启),cmd+N查看模板列表,完成!

默认情况下,k8s创建的服务是无法对公网提供访问的,如果要从公网访问k8s服务,一般有三种方式,NodePort、LoadBalancer、Ingress,下面简单介绍NodePort、LoadBalancer,着重讲解Ingress

本文使用的deployment同官方教程nginx-deployment,图片是从ingress-nginx官方文档拷贝过来借用,如侵必删

NodePort

nodeport

如上图所示,客户端访问集群内任意机器端口30100,k8s自动将请求转发到对应的服务

  1. 创建NodePort类型的service(不指定nodeport的情况下,默认从30000-32767中挑选一个端口使用)
1
kubectl expose deployment nginx-deployment --type="NodePort" --port 80
  1. 查看service kubectl get svc,输出如下
1
2
3
NAME               TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 12d
nginx-deployment NodePort 10.110.255.135 <none> 80:31402/TCP 4s
  1. 验证

外部客户端先访问集群内任意节点端口31402,然后节点会转发到与端口关联的k8s内部服务nginx-deployment。

在物理机上访问服务curl http://vm1:31402,对比在虚拟机如vm1上执行curl http://10.110.255.135,两者的输出是一致的

  1. 删除service
1
kubectl delete svc nginx-deployment

注意:考虑到线上一般是直接通过域名访问,如果不想在域名后还要添加端口访问服务,如www.noname.io:31402,那么还需要使用nginx/haproxy建立一个反向代理的网关进行管理

LoadBalancer

metallb

如上图所示,客户端的请求抵达LoadBalancer指定的IP(这里的IP有点奇怪,应该是与节点IP同一个CIDR但被保留的IP才对,看下面metallb),然后机器将流量转发到对应的服务

一般情况下,各个云服务厂商有提供专门支持k8s的LoadBalancer,本文依赖的k8s是自建的裸集群(bare-metal),默认不支持LoadBalancer,因此需要先安装metallb

metallb安装

  1. 下载配置文件,修改image地址,然后使用kubectl执行
1
2
3
kubectl apply -f ./metallb-native.yaml
# 如果你的网络正常
# kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.8/config/manifests/metallb-native.yaml
  1. 查看pod运行状态
1
kubectl get pods -n metallb-system -o wide

输出如下

1
2
3
4
5
NAME                        READY   STATUS    RESTARTS   AGE   IP                NODE   NOMINATED NODE   READINESS GATES
controller-f6d7bd7b-7kg47 1/1 Running 0 51s 10.0.2.19 vm3 <none> <none>
speaker-8fgzx 1/1 Running 0 51s 192.168.122.235 vm3 <none> <none>
speaker-bfbtk 1/1 Running 0 51s 192.168.122.11 vm1 <none> <none>
speaker-t4lv8 1/1 Running 0 51s 192.168.122.126 vm2 <none> <none>
  1. 配置IP池

配置IP池用于LoadBalancer分配,注意,要确保这部分IP不要被dhcp分配给其他任何机器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# vi ip-address-pool.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: default
namespace: metallb-system
spec:
addresses:
- 192.168.122.20-192.168.122.25
autoAssign: true
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: default
namespace: metallb-system
spec:
ipAddressPools:
- default

执行命令kubectl apply -f ip-address-pool.yaml

到这里就创建好了

实践

  1. 创建LoadBalancer类型的service
1
kubectl expose deployment nginx-deployment --type="LoadBalancer" --port 80
  1. 查看service kubectl get svc,输出如下

192.168.122.20这个地址不是节点IP,而是从IP池里挑选的未被使用的

1
2
3
NAME               TYPE           CLUSTER-IP       EXTERNAL-IP      PORT(S)        AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 12d
nginx-deployment LoadBalancer 10.111.110.163 192.168.122.20 80:30804/TCP 8s
  1. 验证

在物理机上访问服务curl -D- http://192.168.122.20 -H 'Host: www.noname.io',对比在虚拟机上执行curl -D- http://10.111.110.163,两者的输出是一致的

  1. 删除service
1
kubectl delete svc nginx-deployment

Ingress

Ingress在整个k8s架构中扮演着集群网关的角色,提供了如负载均衡、SSL终结和基于名称的虚拟托管功能,当然,你可以用在a/b测试、版本升级这种用途,目前支持的协议是http,后续的继任者Gateway提供了更强大的功能,如支持更多协议、流量管理、更灵活的配置等,但这里暂不做过多介绍

Ingress的运行方式有好几种,其中比较常见的是NodePort以及LoadBalancer。其工作原理是:客户端通过NodePort或者LoadBalancer的方式访问ingress,ingress再通过路由转发到指定的k8s内部服务

本文使用的ingress是ingress-nginx,当然,你也可以选择envoy或higress等

ingress-nginx

将repo下载到本地,地址:https://github.com/kubernetes/ingress-nginx,k8s依赖的部署文件放在`deploy/static/provider/`目录下,NodePort方式的文件放在`baremetal`,而LoadBalancer方式的文件放在`cloud`

  1. 拷贝文件夹baremetalcloud,修改deploy.yaml中的image地址

  2. 执行命令kubectl apply -k .

  3. 查看pod运行状况

1
kubectl get pods -o wide -n ingress-nginx

输出如下,前两个是一次性的job,忽略

1
2
3
4
NAME                                        READY   STATUS      RESTARTS   AGE     IP           NODE   NOMINATED NODE   READINESS GATES
ingress-nginx-admission-create-6mmd6 0/1 Completed 0 2m33s 10.0.2.178 vm3 <none> <none>
ingress-nginx-admission-patch-clnr4 0/1 Completed 0 2m33s 10.0.1.235 vm2 <none> <none>
ingress-nginx-controller-5989548bf5-tjv5t 1/1 Running 0 2m33s 10.0.2.12 vm3 <none> <none>
  1. 查看controller运行状况
1
kubectl get svc ingress-nginx-controller -n ingress-nginx

如果你执行的是cloud配置,也就是LoadBalancer方式,输出如下

1
2
NAME                       TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)                      AGE
ingress-nginx-controller LoadBalancer 10.109.74.112 192.168.122.20 80:31608/TCP,443:30939/TCP 77m

如果你执行的是baremetal配置,也就是NodePort方式,输出如下

1
2
NAME                       TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller NodePort 10.104.202.43 <none> 80:31880/TCP,443:30184/TCP 3m27s
  1. 验证

访问ingress地址

LoadBalancer方式如下

1
2
3
4
5
# LoadBalancer如下
curl -D- http://192.168.122.20 -H 'Host: www.noname.io'

# NodePort如下
curl -D- http://vm1:31880 -H 'Host: www.noname.io'

输出如下

1
2
3
4
5
6
7
8
9
10
11
12
13
HTTP/1.1 404 Not Found
Date: Wed, 28 Aug 2024 04:09:19 GMT
Content-Type: text/html
Content-Length: 146
Connection: keep-alive

<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body>
</html>

到这里,ingress安装完成

实践

  1. 创建默认类型为ClusterIP的服务(该服务无法在集群外部访问,适合验证我们创建的ingress)
1
kubectl expose deployment nginx-deployment
  1. 查看service kubectl get svc,输出如下
1
2
3
NAME               TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 12d
nginx-deployment ClusterIP 10.106.19.73 <none> 80/TCP 12m
  1. 配置ingress规则

该规则指定www.noname.io的所有流量都转发到nginx-deployment服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# example-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
spec:
rules:
- host: www.noname.io
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: nginx-deployment
port:
number: 80
  1. 验证

访问ingress地址,仅展示LoadBalancer方式

1
curl -D- http://192.168.122.20 -H 'Host: www.noname.io'

done

参考文档

3 Ways to Expose Applications Running in Kubernetes Cluster to Public Access
Ingress
在 Minikube 环境中使用 NGINX Ingress 控制器配置 Ingress
Bare-metal considerations
Using Nginx Ingress Controller in Kubernetes bare-metal setup
Using Metal LB on a bare-metal(OnPrem) Kubernetes Setup

在Kubernetes官网的学习教程:wordpress中,我尝试了手动创建pv并绑定pvc,但是每次申请pvc都要手动添加pv并绑定是一件很麻烦且很容易出错的事情,官方也早就意识到这个问题,提出了动态存储分配,只需要通过storageclass即可实现

本文使用了nfs作为集群存储用于实践,线上建议考虑使用ceph等分布式文件存储系统,nfs搭建教程参考:How to Install NFS Server and Client on Ubuntu 22.04

安装nfs provisioner

  1. 确认nfs服务器在集群节点中可以正常访问
1
2
3
4
# 安装客户端
sudo apt install nfs-common
# 执行命令挂载,确认可正常使用
sudo mount 192.168.0.105:/mnt/nfs_share /mnt/nfs_clientshare
  1. 新建一个目录如nfs-example,创建kustomization.yaml文件

鉴于gfw的影响,建议下载github.com/kubernetes-sigs/nfs-subdir-external-provisioner仓库,并将deploy文件夹提取出来,里面主要是账户创建、角色绑定、命名空间、deployment等配置

1
2
3
4
5
# kustomization.yaml
namespace: nfs-provisioner
resources:
# - github.com/kubernetes-sigs/nfs-subdir-external-provisioner//deploy
- ./deploy
  1. 创建命名空间nfs-provisioner
1
2
3
4
5
# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: nfs-provisioner
  1. 创建nfs deployment的patch,主要修改image地址、nfs信息,用于后续的合并
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# patch_nfs_details.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nfs-client-provisioner
name: nfs-client-provisioner
spec:
template:
spec:
containers:
- name: nfs-client-provisioner
image: registry.noname.io:5000/sig-storage/nfs-subdir-external-provisioner:v4.0.2
env:
- name: NFS_SERVER
value: <YOUR_NFS_SERVER_IP>
- name: NFS_PATH
value: <YOUR_NFS_SERVER_SHARE>
volumes:
- name: nfs-client-root
nfs:
server: <YOUR_NFS_SERVER_IP>
path: <YOUR_NFS_SERVER_SHARE>
  1. 添加资源并发布

汇总后的kustomization.yaml内容如下所示

1
2
3
4
5
6
7
8
9
10
# kustomization.yaml
namespace: nfs-provisioner
resources:
- ./deploy
- namespace.yaml
patches:
- path: patch_nfs_details.yaml
target:
kind: Deployment
labelSelector: app=nfs-client-provisioner

执行命令

1
2
3
4
# 发布到集群中
kubectl apply -k .
# 查看pod nfs-client-provisioner是否正常运行
kubectl get pods -n nfs-provisioner

到这里,基于nfs的动态存储分配基本可用

wordpress实践

从教程:wordpress下载相关yaml配置

修改wordpress-deployment.yaml以及mysql-deployment.yaml的pvc配置,在spec下添加storageClassName: nfs-client

当然,如果不添加上述命令,可以将storageclass:nfs-client设置成默认,这样一来就不需要改任何配置了

修改nfs-client为默认

  1. 通过patch修改
1
kubectl patch storageclass nfs-client -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
  1. 修改storageclass的配置文件
1
2
3
4
5
...
metadata:
annotations:
storageclass.kubernetes.io/is-default-class: "true"
...

其他操作不再赘述,到这里就结束了

错误处理

  1. chown: changing ownership of ‘/var/lib/mysql/‘: Operation not permitted

这是因为nfs需要添加no_root_squash权限,修改/etc/exports添加,这个权限太大,不适合线上使用

  1. mount: /mnt/nfs_clientshare: bad option; for several filesystems (e.g. nfs, cifs) you might need a /sbin/mount. helper program.

节点没有安装nfs客户端导致的,安装nfs-common即可

参考文档

nfs-subdir-external-provisioner

简单的配置管理,可以把信息写入文件放在项目里,然后打包发布,但这样会有信息泄露的安全风险,经常会看到谁谁谁把项目上传到github,然后公司数据库/敏感密钥泄漏等。所以,现在的公司一般会自建发布平台,通过发布平台管理配置,流程:配置修改->审核->发布。了解到这背景,我们就知道config跟secret在整个软件开发过程中扮演的角色,接下来就学习如何配置config/secret并使用

当然,一般情况下,配置管理没有那么简单,要考虑多种环境如dev/test/release以及地区区分,操作需小心谨慎,一不小心就出现遗漏、错配,此外,还有多人开发时配置冲突等,这些都是开发过程中要注意的问题

Config

以下展示configMap的简单配置,必须字段apiVersion、kind、metadata、data/binaryData。data可以是key-value键值对,也可以是文件名-文件内容,数据的大小不能超过1MB,具体可以查看官网api描述文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# game-demo.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: game-demo
data:
# 1. key-value
player_initial_lives: "3"
ui_properties_file_name: "user-interface.properties"

# 2. filename-content
game.properties: |
enemy.types=aliens,monsters
player.maximum-lives=5
user-interface.properties: |
color.good=purple
color.bad=yellow
allow.textmode=true

执行命令写入k8s,查看并验证配置信息

1
2
3
4
5
6
# 写入k8s
kubectl apply -f game-demo.yam
#
kubectl get cm game-demo
# 查看config数据
kubectl describe cm game-demo

有了配置信息,怎么搭配container使用?有4种使用方式可供参考

  1. 在容器命令和参数内
  2. 容器的环境变量
  3. 在只读卷里面添加一个文件,让应用来读取
  4. 编写代码在 Pod 中运行,使用 Kubernetes API 来读取 ConfigMap

以下启动一个pod,在启动时打印输出config信息,主要展示的是环境变量跟配置文件两种方式,也就是上面的2和3

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
apiVersion: v1
kind: Pod
metadata:
name: configmap-demo-pod
spec:
containers:
- name: demo
image: registry.noname.io:5000/alpine:3.20.1
command:
- /bin/sh
- "-c"
- |
echo $PLAYER_INITIAL_LIVES;
echo $UI_PROPERTIES_FILE_NAME;
ls -l /config/game.properties;
cat /config/game.properties;
ls -l /config/user-interface.properties;
cat /config/user-interface.properties;
# 方式2:configMap->环境变量
env:
# 定义环境变量
- name: PLAYER_INITIAL_LIVES # 请注意这里和 ConfigMap 中的键名是不一样的
valueFrom:
configMapKeyRef:
name: game-demo # 这个值来自 ConfigMap
key: player_initial_lives # 需要取值的键
- name: UI_PROPERTIES_FILE_NAME
valueFrom:
configMapKeyRef:
name: game-demo
key: ui_properties_file_name
# 方式3:configMap->文件
volumeMounts:
- name: config
mountPath: "/config"
readOnly: true
volumes:
# 你可以在 Pod 级别设置卷,然后将其挂载到 Pod 内的容器中
- name: config
configMap:
# ConfigMap的名字
name: game-demo
# 指定key创建为path文件
items:
- key: "game.properties"
path: "game.properties"
- key: "user-interface.properties"
path: "user-interface.properties"

执行命令

1
2
3
4
5
6
7
8
9
10
11
# 创建pod
kubectl apply -f configmap-demo-pod.yaml
# 查看configmap-demo-pod运行情况
kubectl get pod | grep configmap-demo-pod
# pod是否有其他异常
kubectl describe pod configmap-demo-pod
# 查看command输出日志
kubectl logs configmap-demo-pod
# 清理config、pod
kubectl delete -f game-demo.yaml
kubectl delete -f configmap-demo-pod.yaml

Secret

secret的应用场景想到的只有数据库密码,如果真的要跟数据库其他配置分离也挺蛋疼的,以下简单展示如何使用

secret与config非常相似,有如下相同点

  1. key/value 的形式
  2. 属于某个特定的 namespace
  3. 可以导出到环境变量
  4. 可以通过目录/文件形式挂载 (支持挂载所有 key 和部分 key)

不同点:

  1. secret 可以被 ServerAccount 关联 (使用)
  2. secret 可以存储 register 的鉴权信息,用在 ImagePullSecret 参数中,用于拉取私有仓库的镜像
  3. secret 支持 Base64 加密
  4. secret 文件存储在 tmpfs 文件系统中,Pod删除后secret文件也会对应的删除

配置secret

1
2
3
4
5
6
7
# dotfile-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: dotfile-secret
data:
.secret-file: dmFsdWUtMg0KDQo=

验证

1
kubectl get secret dotfile-secret -o json | jq '.data | map_values(@base64d)'

使用secret,有以下几种方式

  1. 作为挂载到一个或多个容器上的卷中的文件
  2. 作为容器的环境变量
  3. 由kubelet在为Pod拉取镜像时使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# secret-dotfiles-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: secret-dotfiles-pod
spec:
volumes:
- name: secret-volume
secret:
secretName: dotfile-secret
containers:
- name: dotfile-test-container
image: registry.noname.io:5000/alpine:3.20.1
command:
- ls
- "-la"
- "/etc/secret-volume"
# /etc/secret-volume/.secret-file
volumeMounts:
- name: secret-volume
readOnly: true
mountPath: "/etc/secret-volume"

执行命令

1
2
3
4
5
6
7
8
9
10
11
# 创建pod
kubectl apply -f secret-dotfiles-pod.yaml
# 查看secret-dotfiles-pod运行情况
kubectl get pod | grep secret-dotfiles-pod
# pod是否有其他异常
kubectl describe pod secret-dotfiles-pod
# 查看command输出日志
kubectl logs secret-dotfiles-pod
# 清理config、pod
kubectl delete -f dotfile-secret.yaml
kubectl delete -f secret-dotfiles-pod.yaml

参考

ConfigMaps
Secrets

当没有桌面时,ubuntu服务器该如何连接宽带、共享Wi-Fi热点,就像一台路由器一样运作,或者如何连接Wi-Fi,以下简单介绍这几个操作的过程

连接宽带

PPPOE方式

使用nmcli输入pppoe账号密码,这时会打开一个交互式的命令行

1
sudo nmcli con edit type pppoe con-name "Connection name"

输入保存宽带账户相关信息

1
2
3
4
5
6
# 输入账号名密码
set pppoe.username <username>
set pppoe.password <password>
# 保存并推出
save
quit

连接Wi-Fi

1
2
3
4
5
# 列出所有Wi-Fi热点
sudo nmcli dev wifi

# 连接Wi-Fi热点
sudo nmcli dev wifi connect <SSID> <password>

共享Wi-Fi

禁用cloud-init

1
sudo bash -c "echo 'network: {config: disabled}' > /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg"

创建Netplan配置

sudo vi /etc/netplan/10-my-config.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
network:
version: 2
renderer: NetworkManager
ethernets:
eth0:
dhcp4: true
optional: true
wifis:
wlan0:
dhcp4: true
optional: true
access-points:
"Connection Name":
password: "you wifi password"
mode: ap # 如果是作为客户端使用,移除这一行

Netplan配置生效

1
2
sudo netplan generate
sudo netplan apply

Ubuntu Server 18 Wifi Hotspot setup

现在的在线视频网站为了打击盗版、保护资源/网络流量,不再直接使用mp4等视频格式,而是转而使用m3u8文件(播放索引列表)+ts文件(视频分段技术),其中有的视频还用了加密,下面简单展示下如何下载ts文件并合并为mp4

m3u8索引

从浏览器的dev tool找到m3u8文件的连接并下载,该m3u8文件内有所有的ts文件列表,你可以将他们复制出来,通过编辑器补全制作成完整的url,通过下载器批量下载

未加密

如果视频没有加密(m3u8文件中#EXT-X-KEY字段为空),那么在下载所有ts文件后,可以直接用linux命令合并

1
2
3
4
# ts段一般都有固定格式,如example_1.ts
echo 'ts_link_in_m3u8_file_'{number_first..number_end}.ts | tr "" "\n" > tslist
# 将所有相关的ts文件合并
while read line; do cat $line >> the_final_video.mp4; done < tslist

到这里就不需要再往下看了

加密

如果视频加密(m3u8文件中#EXT-X-KEY字段不为空),如下所示

1
2
3
4
5
6
7
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-KEY:METHOD=AES-128,URI="https://hentai.com/xxxxxx",IV=0xa746ff934e7fff621ba2cf5a32608914
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-TARGETDURATION:6
#EXTINF:5.881,
https://hentai.com/xxxxxxyyyyyyyy.ts

处理步骤:

  1. 下载必须的文件
  2. 修改m3u8文件
  3. 使用ffmpeg合并

下载必须的文件

  1. 通过浏览器dev tool找到m3u8文件url
  2. 复制m3u8文件内所有ts文件地址到下载器下载,这里可能还需要通过编辑器补全url
  3. 复制m3u8文件内记录的key的地址(从EXT-X-KEY字段获取链接)到下载器下载,可进一步重命名,如改为crypt.key,该文件文件大小一般是16字节

修改m3u8文件

  1. 修改key的地址,将url改为本地文件路径
  2. 如果ts文件url是http链接,那么也改为本地文件路径

示例如下:

1
2
3
4
5
6
7
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-KEY:METHOD=AES-128,URI="crypt.key",IV=0xa746ff934e7fff621ba2cf5a32608914
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-TARGETDURATION:6
#EXTINF:5.881,
xxxxxxyyyyyyyy.ts

使用ffmpeg合并

1
ffmpeg -allowed_extensions ALL -protocol_whitelist "file,http,crypto,tcp" -i example.m3u8 -c copy the_final_video.mp4

done

参考文档

记一次加密的ts视频下载

部署计划:先创建一台虚拟机vm1,安装docker以及k8s组件,然后克隆两台新的vm2/vm3,组合成一个小型的k8s集群

虚拟机准备

创建vm1

创建虚拟机vm1,以下就不展示繁琐的系统安装过程了

kvm create vm1

配置vm1

根据教程:安装docker并部署registry服务安装好containerd,再执行下面的配置

系统配置修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# linux 模块
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

# lsmod | grep overlay
# lsmod | grep br_netfilter

# 网络配置
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF

cat /etc/sysctl.d/k8s.conf

sudo sysctl --system

# sudo sysctl -a | grep net.ipv4.ip_forward

关闭swap

关闭swap并移除文件

1
2
3
4
5
6
7
8
9
10
11
12
13
# 查看swap文件位置
sudo swapon

# 关闭swap
sudo swapoff /swap.img

# 移除swap文件
sudo rm /swap.img

# 开机启动项注释
sudo vi /etc/fstab
# 删除或注释下面行
#/swap.img none swap sw 0 0

registry证书导入

私有registry使用self-signed证书,kubeadm拉取时会报错,故将证书添加到vm

1
2
3
4
5
6
7
8
# 物理机拷贝证书到vm1
scp noname.io.crt noname@192.168.122.11:/home/noname/

# vm1 复制到系统目录
sudo cp noname.io.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
sudo systemctl restart containerd
sudo systemctl restart docker # 可选

containerd设置

修改/etc/containerd/config.toml,设置cgroup driver为systemd,同时可以修改pause版本,version是必须的,否则不生效,缩进非必要,可以删除

1
2
3
4
5
6
7
8
9
10
version = 2

[plugins]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true

[plugins."io.containerd.grpc.v1.cri"]
sandbox_image = "registry.noname.io:5000/pause:3.10" # 默认是3.8,这里可以不修改

重启containerd服务

1
sudo systemctl restart containerd

执行命令输出生效中的配置,验证配置是否跟预期一致

1
containerd config dump | grep SystemdCgroup

docker配置(可选)

k8s与docker分手后,vm只需要一个containerd就够了,但如果你同时也安装了docker,也可以修改/etc/docker/daemon.json

1
2
3
4
{
"exec-opts": ["native.cgroupdriver=systemd"],
"insecure-registries" : ["registry.noname.io:5000"]
}

重启docker服务

1
sudo systemctl restart docker

镜像准备(可选)

为了避免kubeadm init长时间卡在镜像拉取上,可以先在物理机上通过proxy拉取到本地,再推送到私有registry,最后配置从私有registry拉取image

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
# k8s需要的镜像,可以通过kubeadm config images list打印
docker pull registry.k8s.io/kube-apiserver:v1.31.0
docker pull registry.k8s.io/kube-controller-manager:v1.31.0
docker pull registry.k8s.io/kube-scheduler:v1.31.0
docker pull registry.k8s.io/kube-proxy:v1.31.0
docker pull registry.k8s.io/coredns/coredns:v1.11.1
docker pull registry.k8s.io/pause:3.10
docker pull registry.k8s.io/etcd:3.5.15-0
# cilium依赖的镜像
docker pull quay.io/cilium/cilium:v1.15.6
docker pull quay.io/cilium/operator-generic:v1.15.6

# 重新打tag
docker tag registry.k8s.io/kube-apiserver:v1.31.0 registry.noname.io:5000/kube-apiserver:v1.31.0
docker tag registry.k8s.io/kube-controller-manager:v1.31.0 registry.noname.io:5000/kube-controller-manager:v1.31.0
docker tag registry.k8s.io/kube-scheduler:v1.31.0 registry.noname.io:5000/kube-scheduler:v1.31.0
docker tag registry.k8s.io/kube-proxy:v1.31.0 registry.noname.io:5000/kube-proxy:v1.31.0
docker tag registry.k8s.io/coredns/coredns:v1.11.1 registry.noname.io:5000/coredns:v1.11.1
docker tag registry.k8s.io/pause:3.10 registry.noname.io:5000/pause:3.10
docker tag registry.k8s.io/etcd:3.5.15-0 registry.noname.io:5000/etcd:3.5.15-0
docker tag quay.io/cilium/cilium:v1.15.6 registry.noname.io:5000/cilium/cilium:v1.15.6
docker tag quay.io/cilium/operator-generic:v1.15.6 registry.noname.io:5000/cilium/operator-generic:v1.15.6

# 推送至私有registry
docker push registry.noname.io:5000/kube-apiserver:v1.31.0
docker push registry.noname.io:5000/kube-controller-manager:v1.31.0
docker push registry.noname.io:5000/kube-scheduler:v1.31.0
docker push registry.noname.io:5000/kube-proxy:v1.31.0
docker push registry.noname.io:5000/coredns:v1.11.1
docker push registry.noname.io:5000/pause:3.10
docker push registry.noname.io:5000/etcd:3.5.15-0
docker push registry.noname.io:5000/cilium/cilium:v1.15.6
docker push registry.noname.io:5000/cilium/operator-generic:v1.15.6

在虚拟机vm1中使用critool导入镜像,示例如下

1
2
3
4
5
6
7
8
# 查看containerd镜像
sudo crictl --runtime-endpoint unix:///var/run/containerd/containerd.sock image

# 拉取镜像到containerd
sudo crictl --runtime-endpoint unix:///var/run/containerd/containerd.sock pull registry.noname.io:5000/quay.io/cilium/operator-generic:v1.15.6

# 重新打tag
sudo ctr --namespace=k8s.io image tag registry.noname.io:5000/quay.io/cilium/operator-generic:v1.15.6 quay.io/cilium/operator-generic:v1.15.6

安装组件

apt方式安装(推荐)

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
# 
sudo apt update -y && sudo apt upgrade -y

#
sudo apt install -y apt-transport-https ca-certificates curl gpg

# 先创建好目录,如果没有的话
# sudo mkdir -p -m 755 /etc/apt/keyrings

# curl这里需要使用http_proxy/https_proxy
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

# 默认会覆盖已存在的/etc/apt/sources.list.d/kubernetes.list
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.31/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list

sudo apt update

# 方式一 安装最新版,同时会安装ebtables cri-tools kubernetes-cni组件
sudo apt install -y kubelet kubeadm kubectl

# 方式二 指定版本
# sudo apt install -y kubelet=1.31.0* kubeadm=1.31.0* kubectl=1.31.0*

# 同时检查下xzip ethtool socat conntrack net-tools这几个组件是否安装
# sudo apt install -y xzip ethtool socat conntrack net-tools

# 锁定版本
sudo apt-mark hold kubelet kubeadm kubectl

sudo systemctl enable --now kubelet

手动安装

重点/usr/local/bin以及/opt/cni/bin这两个目录树以及所有文件要确保所有者是root,否则会导致部署失败

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
# 复制软件到vm
# cd Downloads/
scp kube* kubelet.service 10-kubeadm.conf noname@192.168.122.11:/home/noname/ # kubernetes相关
scp crictl-v1.30.1-linux-amd64.tar.gz noname@192.168.122.11:/home/noname/ # cri-tools
scp cni-plugins-linux-amd64-v1.5.1.tgz noname@192.168.122.11:/home/noname/ # kubernetes-cni
scp cilium-linux-v0.16.13.tar.gz noname@192.168.122.11:/home/noname/ # cilium
scp helm-v3.15.3-linux-amd64.tar.gz noname@192.168.122.11:/home/noname/ #

# kubernetes-cni
sudo mkdir -p /opt/cni/bin
sudo tar -C /opt/cni/bin -xzf cni-plugins-linux-amd64-v1.5.1.tgz

# cri-tools
sudo tar -C /usr/local/bin -xzf crictl-v1.31.1-linux-amd64.tar.gz

# cilium
sudo tar -C /usr/local/bin -xzf cilium-linux-v0.16.13.tar.gz

# helm
tar zxf helm-v3.15.3-linux-amd64.tar.gz && sudo mv linux-amd64/helm /usr/local/bin/ && rm -rf linux-amd64/

# 将kubeadm等组件放置到指定目录
sudo mv kube* /usr/local/bin/ && chmod +x /usr/local/bin/kube*

#
sudo mkdir -p /var/lib/kubelet
# 修改配置文件中的bin目录并放置到指定位置
cat kubelet.service | sed "s:/usr/bin:/usr/local/bin:g" | sudo tee /usr/lib/systemd/system/kubelet.service

sudo mkdir -p /usr/lib/systemd/system/kubelet.service.d
# 同上
cat 10-kubeadm.conf | sed "s:/usr/bin:/usr/local/bin:g" | sudo tee /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf

sudo systemctl enable --now kubelet

# 确保所有者是root
sudo chown -R root:root /opt/cni
sudo chown -R root:root /usr/local/bin

克隆vm1

以上初步准备安装好了k8s,现在还不能执行初始化部署,复制两个新的vm,避免每台机器都重复执行上面的操作,偷懒

新的vm需要确保跟旧的vm1不能有相同的MAC地址、hostname、product_uuid

hostname重命名

修改hostname

1
2
3
sudo hostnamectl hostname vm1
# sudo hostnamectl hostname vm2
# sudo hostnamectl hostname vm3

重新生成machine-id

dhcp获取IP时,依赖的是machine-id,如果三台机器的machine-id都一样,你会发现所有vm的IP地址都是一样的,会出现冲突,比如ssh突然断线

先关闭其他机器如vm1,使用同样ssh命令如ssh noname@192.168.122.11连接到新的vm2/vm3

1
2
3
4
5
6
7
8
9
# 需要确保/etc/machine-id与/var/lib/dbus/machine-id一致,软连接来的,只要改一个
sudo rm /etc/machine-id
sudo dbus-uuidgen --ensure=/etc/machine-id

# 再次确认下,确定三台机器的product_uuid都不一样
sudo cat /sys/class/dmi/id/product_uuid

# 重启机器
sudo reboot now

cockpit绑定MAC以及IP

点击添加【静态主机条目】,绑定mac地址与ip地址,避免重启一次机器就变一次IP

bind ip and mac

hosts修改

往hosts添加三台vm的host映射,移除127.0.0.1 vm1字样的的记录

1
2
3
4
5
192.168.0.105 registry.noname.io

192.168.122.11 vm1
192.168.122.126 vm2
192.168.122.235 vm3

部署k8s

好了,到这里就可以开始使用kubeadm初始化部署k8s集群了

执行kubeadm init

方法一,直接运行下面命令,比较适合简单的集群配置,不涉及太多配置修改

1
sudo kubeadm init --skip-phases addon/kube-proxy --apiserver-advertise-address 192.168.122.11 --node-name vm1 --image-repository registry.noname.io:5000

方法二,通过命令导出默认配置内容,修改相关配置,最后指定config文件运行

1
2
# 导出默认配置
kubeadm config print init-defaults > kubeadm_config.yaml

把advertiseAddress、name、skipPhases、imageRepository等需要修改的地方全改了

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
apiVersion: kubeadm.k8s.io/v1beta3
bootstrapTokens:
- groups:
- system:bootstrappers:kubeadm:default-node-token
token: abcdef.0123456789abcdef
ttl: 24h0m0s
usages:
- signing
- authentication
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 192.168.122.11
bindPort: 6443
nodeRegistration:
criSocket: unix:///var/run/containerd/containerd.sock
imagePullPolicy: IfNotPresent
name: vm1
taints: null
skipPhases:
- addon/kube-proxy
---
apiServer:
timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta3
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controllerManager: {}
dns: {}
etcd:
local:
dataDir: /var/lib/etcd
imageRepository: registry.noname.io:5000
kind: ClusterConfiguration
kubernetesVersion: 1.31.0
networking:
dnsDomain: cluster.local
serviceSubnet: 10.96.0.0/12
scheduler: {}

dry-run验证kubeadm_config.yaml文件

1
sudo kubeadm init --config kubeadm_config.yaml --dry-run

执行初始化

1
sudo kubeadm init --config kubeadm_config.yaml

正常情况下,只要没有中途退出报错,走到这里就是安装成功的第一步了。如果出现异常可以在初始化命令后面加-v 5打印更具体的报错信息,我也把遇到过的报错情况放到了最后的bug处理,接下来就是验证集群是否在正常工作了

PS:kubeadm提示可以在执行init命令前可以先拉取image,我试了一下kubeadm config images pull,发现init还是会去拉取image,上面就不在记录这个步骤

集群状态验证

配置bash

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

执行命令,验证集群的工作状态。注意:安装过程中如果有使用proxy会导致kubectl连接不上api server,导致超时,需要删除环境变量unset http_proxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 查看6443端口的监听情况
sudo netstat -tnlp | grep 6443

# 导出集群状态
kubectl cluster-info

# 查看所有节点信息
kubectl get nodes -o wide
kubectl describe nodes

# 由于现在还没有安装cilium cni,此时coredns应处于pending状态
kubectl get pods -A -o wide

# 如果有pod反复崩溃重启,查看相关日志
kubectl logs <pod名称> -n <namespace> -p
kubectl describe <pod名称> -n <namespace> -p

# 也可以通过crictl查看container,操作类似docker
sudo crictl --runtime-endpoint unix:///var/run/containerd/containerd.sock ps -a

安装cilium

下载cilium,安装到/usr/local/bin/

1
2
3
4
5
6
# 可能需要proxy
curl -L --fail --remote-name-all https://github.com/cilium/cilium-cli/releases/download/v0.16.15/cilium-linux-amd64.tar.gz{,.sha256sum}
sha256sum --check cilium-linux-amd64.tar.gz.sha256sum

# 省略目录操作了
sudo mv cilium /usr/local/bin/

cilium查看组件安装情况以及报错信息,这里可以根据输出拉取相关image推送到私有registry,cni目录需要保证所有者是root

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 查看cilium状态、错误
cilium status

# 确保/opt/cni目录的所有者是root
# sudo chown -R root:root /opt/cni

# 集群安装cilium
cilium install
# 指定版本安装
# cilium install v1.15.6

# 查看pod是否准备就绪-running
kubectl get pods -A -o wide

# 节点状态应变为-Ready
kubectl get nodes

到这里集群的初始化就完成了,congratulations

其他

单节点k8s

如果你不想要组集群了,想使用单个节点,那么移除taint即可

1
2
3
kubectl taint nodes --all node-role.kubernetes.io/control-plane-
# 这个taint未必存在
kubectl taint nodes --all node-role.kubernetes.io/master-

配置proxy拉取镜像

如果你不想使用私有的registry,那么可以使用proxy,containerd的配置如下

1
2
3
4
sudo systemctl set-environment HTTP_PROXY=192.168.0.105:8118
sudo systemctl set-environment HTTPS_PROXY=192.168.0.105:8118

sudo systemctl restart containerd

然后,不论是直接执行kubeadm init还是kubeadm config images pull,拉取镜像都会通过proxy拉取

Dashboard UI

安装

方法一:使用helm安装(官方推荐)

1
2
3
4
# 添加 kubernetes-dashboard 仓库
helm repo add kubernetes-dashboard https://kubernetes.github.io/dashboard/
# 使用 kubernetes-dashboard Chart 部署名为 `kubernetes-dashboard` 的 Helm Release
helm upgrade --install kubernetes-dashboard kubernetes-dashboard/kubernetes-dashboard --create-namespace --namespace kubernetes-dashboard

方法二:下载配置文件导入

1
2
# 可以下载后传送到vm
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml

执行kubectl get pods -A,你会在kubernetes-dashboard命名空间下看到两个pod

1
2
3
NAMESPACE              NAME                                         READY   STATUS    RESTARTS      AGE     IP                NODE   NOMINATED NODE   READINESS GATES
kubernetes-dashboard dashboard-metrics-scraper-6b96ff7878-ct999 1/1 Running 0 6m57s 10.0.1.215 vm2 <none> <none>
kubernetes-dashboard kubernetes-dashboard-8696f5f494-8cx4d 1/1 Running 0 6m57s 10.0.1.62 vm2 <none> <none>

如果无法拉取image,可以在物理机下载后推送到私有registry

1
2
docker pull kubernetesui/dashboard:v2.7.0
docker pull kubernetesui/metrics-scraper:v1.0.8
创建用户

新建文件dashboard-user.yaml,创建用户、绑定角色,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: ServiceAccount
metadata:
name: dashboard-user
namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: dashboard-user
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: dashboard-user
namespace: kubernetes-dashboard

执行命令

1
kubectl apply -f dashboard-user.yaml
访问dashboard
1
2
3
4
5
6
7
8
9
kubectl proxy
# 新版本限制死了只能在执行了kubectl proxy的机器上打开,没什么意义
# kubectl proxy --address 0.0.0.0 --accept-hosts '.*'

# 获取登陆的token
kubectl -n kubernetes-dashboard create token dashboard-user

# 物理机建立ssh port forwarding
ssh -L 8001:127.0.0.1:8001 noname@192.168.122.11

在物理机浏览器打开dashboard页面:http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/ ,输入token即可进入dashboard界面

部署vm2/vm3

打印vm1的k8s token信息

1
kubeadm token create --print-join-command

在vm2/vm3执行命令,加入集群

1
2
sudo kubeadm join 192.168.122.11:6443 --token 1kf3mx.6omg1ycbi6xul19q \
--discovery-token-ca-cert-hash sha256:9a412686425ed09f8156ec63cb52e798a61cf8de85069be0e36ec023f806ea4f

在vm1验证集群状态

1
2
kubectl get nodes
kubectl get pods -n kube-system

练习

以下列出几个简单的app部署教程,从简单到复杂

  1. nginx-deployment

该教程是最简单的deployment使用教程,提供的功能是一个简单的http服务器,对外输出静态html页面,pod只有一个nginx的container,不涉及多container交互、存储等,该教程是一个很好的入门切入点

  1. guestbook

该教程会教你如何部署一个redis主从服务、php服务,最后创建一个可对外的service,简单的说,是教你多个container如何交互并提供对外访问能力(集群内),这里不涉及存储、ingress等。应用提供的功能可以理解为动态http服务器,php从redis读取数据输出html页面、写入数据到redis

  1. wordpress

该教程会教你如何创建持久卷保存数据,涉及mysql部署、wordpress部署、创建secret,到这里基本涵盖到平时大多数的使用场景了

注意:由于该教程是基于minikube的,直接部署到自建的k8s集群会有很多问题,比如

  • 不支持LoadBalancer,我们需要删除掉wordpress-deployment.yaml文件service声明配置里的type: LoadBalancer
  • 缺少默认的storageclass,不支持动态制备,只能手动建立pv,然后在pvc关联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
# 把下面这部分配置插入到wordpress-deployment.yaml的pvc声明前面
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-pv
spec:
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /data/k8s/pv/mysql-pv
# 同时往pvc的spec部分添加下面这句话
# volumeName: mysql-pv
---
# 把下面这部分配置插入到mysql-deployment.yaml的pvc声明前面
apiVersion: v1
kind: PersistentVolume
metadata:
name: wp-pv
spec:
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /data/k8s/pv/wp-pv
# 同时往pvc的spec部分添加下面这句话
# volumeName: wp-pv

最后,在vm1节点执行命令创建端口转发

1
2
3
4
# 映射到vm1
kubectl port-forward svc/wordpress 8888:80
# 映射到物理机
ssh -L 8888:127.0.0.1:8888 noname@192.168.122.11

done!

后记

注意,按照以上教程安装的集群属于裸集群(bare-metal),默认不支持以下几个功能

  • Load Balancer Service
  • Persistent Volume
  • Ingress/Gateway

如果你的角色是开发者,还是老老实实使用Minikube,避免耗费太多时间精力在集群运维上

另外,一个完整的运维系统不仅仅只是k8s,应当包含以下组件

  • k8s,负责管理pod资源(cpu/内存)、node资源
  • 分布式文件系统,负责持久存储
  • gateway,负责控制外部流量对集群内服务的访问
  • elk,负责日志收集、分析、告警
  • prometheus,负责收集指标、链路分析、告警
  • ci/cd系统,如gitlab,负责开发-发布系统流程
  • 管理后台,负责应用管理、发布审核、记录审计信息等

bug处理

  1. kubeadm init执行后集群组件反复崩溃重启CrashLoopBackOff或者是卡在kubelet-check检查步骤

该问题原因与下面的cilium报错原因一致,软件权限导致的。在最开始时我使用的是手动安装方式,软件都是解压缩复制到bin目录,没有注意到所有组件的owner不是root,这才导致了上面这个问题。虽然是一个很容易忽视的问题,但却让我花了很长时间去debug,太亏了

  1. cilium安装报错cp: cannot create regular file '/hostbin/cilium-mount': Permission denied

这是因为这个目录是我手动创建的,在执行apt安装前就存在,目录的owner不是root,需要确保虚拟机/opt/cni目录的所有者是root,执行sudo chown -R root:root /opt/cni解决,详细看:https://github.com/cilium/cilium/issues/23838

  1. kubeadm join报错'/run/systemd/resolve/resolv.conf': No such file or directory

从服务器我换了一个debian,毕竟线上服务器通常都不是同一类系统,而这次报错的服务器bookworm就没有安装systemd-resolved,下载安装即可

1
sudo apt install systemd-resolved

上面debian的修改会引入另一个问题,就是vm无法连通外网了,发现是dhcp有异常,进而导致ImagePullBackOff,也不知道为什么不能直接读取本地的image,需要使用systemd-networkd才行,详细看问题描述Configuring systemd-networkd on Debian

1
2
3
4
5
# 备份
mv /etc/network/interfaces /etc/network/interfaces.bak

# 新增systemd-dhcp配置
sudo vi /etc/systemd/network/dhcp.network

dhcp.network文件内容如下

1
2
3
4
5
6
# 
[Match]
Name=en*

[Network]
DHCP=yes

设置开机启动并重启服务

1
2
sudo systemctl enable systemd-networkd
sudo systemctl restart systemd-networkd
  1. nginx-deployment报错ImagePullBackOff

这个问题很奇怪,除了前面提到的网络问题,还可能是hash值不匹配

当时我改了nginx-deployment.yaml里的镜像,而且确认本地已经拉取过来了,但部署时就一直报错,最后我在物理机重新拉取镜像时发现hash值居然对不上,最后重新打tag推送才解决

参考文档

安装 kubeadm
Simple Single-node Kubernetes Cluster via kubeadm on Ubuntu 22.04
How to Install Kubernetes Cluster on Debian 12 | 11
cilium在kubernetes中的生产实践二(cilium部署)
The differences between Docker, containerd, CRI-O and runc
Kubernetes Dashboard: Tutorial, Best Practices & Alternatives
Creating sample user
Is it OK to change /etc/machine-id?
Kubernetes The Hard Way

安装

前置依赖

系统是ubuntu 24.04 LTS,开机启动使用了systemd,所以先安装相关依赖包

1
sudo apt install libsystemd-dev

编译安装

官网推荐使用pre-packaged方式,稳定可靠,适合生产环境。不过我想用最新版,就直接用源码编译安装了

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
# 进入源代码目录
cd postgresql-16.4

# 指定安装目录、设置支持systemd
./configure --prefix=/usr/local/postgresql-16.4 --with-systemd --with-ssl=openssl

# 编译安装
make
sudo make install

# 软连接,方便升级用
sudo ln -s /usr/local/postgresql-16.4 /usr/local/pgsql

# 创建用户
sudo adduser postgres

# 数据库数据存储目录,与app目录分离
sudo mkdir -p /var/lib/pgsql/data
sudo chown -R postgres /var/lib/pgsql

# 切换到postgres用户
su - postgres

# 初始化数据库目录、启动实例
/usr/local/pgsql/bin/initdb -D /var/lib/pgsql/data
/usr/local/pgsql/bin/pg_ctl -D /var/lib/pgsql/data -l logfile start
# 验证
/usr/local/pgsql/bin/psql
# 关闭实例
# /usr/local/pgsql/bin/pg_ctl -D /var/lib/pgsql/data/ -l logfile stop

到这里,postgresql完成安装、初始化

systemd配置

开机启动配置systemd,添加配置文件/etc/systemd/system/postgresql.service,内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Unit]
Description=PostgreSQL database server
Documentation=man:postgres(1)
After=network-online.target
Wants=network-online.target

[Service]
Type=notify
User=postgres
ExecStart=/usr/local/pgsql/bin/postgres -D /var/lib/pgsql/data
ExecReload=/bin/kill -HUP $MAINPID
KillMode=mixed
KillSignal=SIGINT
TimeoutSec=infinity

[Install]
WantedBy=multi-user.target

使用systemd启动postgresql

1
2
3
sudo systemctl daemon-reload
sudo systemctl start postgresql
sudo systemctl enable postgresql

pgsql配置

以上完成pg安装,本地可以直接访问使用。接下来是配置数据库提供对外访问能力,如配置tcp监听地址、添加用户、配置权限等

进入pg的data数据存储目录,修改相关配置文件

tcp配置

修改postgresql.conf文件,配置监听地址

1
2
# 默认localhost,改为'*'
listen_addresses = '*'

auth配置

PS: pg的授权配置没有像mysql一样开箱即用,先简单配置下吧

修改pg_hba.conf文件,开放数据库/用户对外访问权限

1
2
3
# TYPE  DATABASE        USER            ADDRESS                 METHOD
host blogdb all 0.0.0.0/0 scram-sha-256
host blogdb all ::0/0 scram-sha-256

重启postgresql

1
sudo systemctl start postgresql

修改超级用户密码或者创建新角色并授权

1
2
3
4
5
6
7
8
9
10
11
12
13
-- 1. 修改postgres密码
-- alter user postgres password 'your_password';

-- 2. 创建新角色并授权
\c blogdb;

CREATE ROLE blog_read_only WITH LOGIN PASSWORD 'your_password';
CREATE ROLE blog_read_write WITH LOGIN PASSWORD 'your_password';
CREATE ROLE blog_admin WITH LOGIN PASSWORD 'your_password';

GRANT SELECT ON ALL TABLES IN SCHEMA public TO blog_read_only;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO blog_read_write;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO blog_admin;

curd example

完成了pg的安装配置,接下来就是写一个demo测试了,首先,找一个数据表导入

1
2
3
# 获取countries.csv数据
curl 'https://raw.githubusercontent.com/lukes/ISO-3166-Countries-with-Regional-Codes/master/all/all.csv' \
--output ./countries.csv

创建数据库blogdb,并导入countries.csv

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
create database blogdb;
\c blogdb;

create table countries (
id integer primary key generated always as identity,
name text not null unique,
alpha_2 char(2) not null,
alpha_3 char(3) not null,
numeric_3 char(3) not null,
iso_3166_2 text not null,
region text,
sub_region text,
intermediate_region text,
region_code char(3),
sub_region_code char(3),
intermediate_region_code char(3)
);

copy countries (
name,
alpha_2,
alpha_3,
numeric_3,
iso_3166_2,
region,
sub_region,
intermediate_region,
region_code,
sub_region_code,
intermediate_region_code
)
from '/var/lib/pgsql/data/countries.csv'
delimiter ',' csv header;

-- Check that the data got loaded into the table ok.
select * from countries limit 10;

-- Should say 249.
select count(*) from countries;

使用golang写一个简单的app,查询countries数据,到这里完成!

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
package main

import (
"context"
"fmt"

"github.com/jackc/pgx/v5"
)

func main() {
ctx := context.Background()

conn, err := pgx.Connect(ctx, "postgres://postgres:your_password@192.168.33.10:5432/blogdb?sslmode=disable")
if err != nil {
panic(err)
}
defer conn.Close(ctx)

rows, err := conn.Query(ctx, "SELECT id, name FROM countries LIMIT 10")
if err != nil {
panic(err)
}
defer rows.Close()

for rows.Next() {
var id int
var name string
err = rows.Scan(&id, &name)
if err != nil {
panic(err)
}
fmt.Println(id, "-", name)
}
}

其他

开启ssl

如果想要在本地提前体验ssl加密配置的过程,那么按照下面的流程,先生成self-signed证书、私钥,最后修改配置

1
2
3
4
5
6
7
8
9
10
11
su - postgres

# 进入数据存储目录
cd /var/lib/pgsql/data

# 生成self-signed ssl相关证书、私钥
openssl req -new -x509 -days 365 -nodes -text -out server.crt \
-keyout server.key -subj "/CN=dbhost.yourdomain.com"

# 移除other、group的访问权限
chmod og-rwx server.key

修改postgresql.conf文件,取消下面三行注释,需要保证证书放置在data目录,生产环境如果使用授权ca证书,修改的命令就不是ssl_cert_file以及ssl_key_file,按官网描述修改

1
2
3
ssl = on
ssl_cert_file = 'server.crt'
ssl_key_file = 'server.key'

最后,demo里dsn移除sslmode=disable,然后重新运行验证

安装docker

当前仅展示apt方式安装,离线安装方式不展示

清理历史残留

如果系统有预装docker或者先前安装过,先卸载干净在安装

1
2
3
4
5
6
## 第一次安装时输出为空
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt remove $pkg; done

## 如果以前有安装过docker,相关数据可以删除掉
sudo rm -rf /var/lib/docker
sudo rm -rf /var/lib/containerd

apt安装

设置代理

由于docker被gfw屏蔽,访问时需要使用vpn,这里使用先前搭建的ss服务

新建apt.conf

1
sudo vi /etc/apt/apt.conf

添加proxy配置,默认源地址配置直连,如此后续配置docker源后,使用apt update便可直接访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Acquire::http::Proxy "http://127.0.0.1:8118";
Acquire::http::Proxy {
archive.ubuntu.com DIRECT;
security.ubuntu.com DIRECT;
cn.archive.ubuntu.com DIRECT;
mirrors.tuna.tsinghua.edu.cn DIRECT;
}
Acquire::https::Proxy "http://127.0.0.1:8118";
Acquire::https::Proxy {
archive.ubuntu.com DIRECT;
security.ubuntu.com DIRECT;
cn.archive.ubuntu.com DIRECT;
mirrors.tuna.tsinghua.edu.cn DIRECT;
}

配置源并安装

按照docker官方教材安装docker,其中,下载docker.asc的命令做了一些修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Add Docker's official GPG key:
sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
# 配置proxy
export http_proxy=http://127.0.0.1:8118;export https_proxy=http://127.0.0.1:8118;
# 下载后在使用sudo搬到指定目录
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o docker.asc && sudo mv docker.asc /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt update

# 如果是k8s部署,只需要containerd就够了
sudo apt install containerd.io docker-ce docker-ce-cli docker-buildx-plugin docker-compose-plugin

后续

将当前用户加入docker用户组

1
2
sudo groupadd docker
sudo usermod -aG docker $USER

部署registry

配置代理

因为gfw的原因,docker hub的访问会时不时抽风,在使用docker pull前可以先配置代理,当前步骤可跳过

创建配置文件

1
2
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo vi /etc/systemd/system/docker.service.d/http-proxy.conf

配置proxy

1
2
3
4
[Service]
Environment="HTTP_PROXY=http://127.0.0.1:8118"
Environment="HTTPS_PROXY=http://127.0.0.1:8118"
Environment="NO_PROXY=localhost,127.0.0.1,registry.noname.io"

重启并验证docker环境变量

1
2
3
4
5
sudo systemctl daemon-reload
sudo systemctl restart docker
sudo systemctl show --property=Environment docker
# 输出如下
# Environment=HTTP_PROXY=http://127.0.0.1:8118 HTTPS_PROXY=http://127.0.0.1:8118 NO_PROXY=localhost,127.0.0.1,registry.noname.io

生成ssl证书

可以直接使用http,但后续k8s镜像的下载必须使用https,没有那么多耐心再去折腾,在这里先解决了,一劳永逸

/data/docker/是我用于专门存放与registry相关的数据,包括certs以及后续docker push上来的镜像数据

下面命令关键的域名registry.noname.io要填好

1
2
3
4
openssl req \
-newkey rsa:4096 -nodes -sha256 -keyout /data/docker/certs/noname.io.key \
-addext "subjectAltName = DNS:registry.noname.io" \
-x509 -days 365 -out /data/docker/certs/noname.io.crt

由此,https所需要的证书也就准备好了

创建registry服务

注意

  1. /data/docker/certs挂载到container的/data/certs目录
  2. /data/docker/registry挂载到container的/var/lib/registry目录,后续删除重建container时就保留下了image数据

方法一

如果只是创建一个简单的可运行registry,直接运行docker run

1
2
3
4
5
6
docker run -d -p 5000:5000 --restart always --name registry \
-v /data/docker/registry:/var/lib/registry \
-v /data/docker/certs:/data/certs \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/data/certs/noname.io.crt \
-e REGISTRY_HTTP_TLS_KEY=/data/certs/noname.io.key \
registry:2.8.3

方法二

如果涉及比较复杂的配置,像权限管理、接入s3文件系统、缓存配置、中继配置等,则使用配置文件方式比较好

创建registry配置文件,参考配置文件:example YAML file,修改后如下

1
2
3
4
5
6
7
8
9
version: 0.1
storage:
filesystem:
rootdirectory: /var/lib/registry
http:
addr: :5000
tls:
certificate: /data/certs/noname.io.crt
key: /data/certs/noname.io.key

运行docker run

1
2
3
4
5
docker run -d -p 5000:5000 --restart always --name registry \
-v /data/docker/registry:/var/lib/registry \
-v /data/docker/certs:/data/certs \
-v /data/docker/config/config.yml:/etc/docker/registry/config.yml \
registry:2.8.3

修改系统配置

修改hosts

1
sudo vi /etc/hosts

添加私有registry的域名

1
127.0.0.1 registry.noname.io

修改docker配置

1
2
sudo mkdir /etc/docker
sudo vi /etc/docker/daemon.json

insecure-registries需要添加registry的域名端口

1
2
3
4
{
"exec-opts": ["native.cgroupdriver=systemd"],
"insecure-registries" : ["registry.noname.io:5000"]
}

重启服务

1
sudo systemctl restart docker

如果要开放局域网内的访问,配置防火墙

1
2
sudo ufw allow 5000/tcp
sudo ufw reload

测试验证

访问registry接口,此时因为registry还没有任何image数据,输出为空

1
curl -k https://registry.noname.io:5000/v2/_catalog | jq .

推送镜像测试

1
2
docker tag alpine:3.20.1 registry.noname.io:5000/alpine:3.20.1
docker push registry.noname.io:5000/alpine:3.20.1

参考文档

Install Docker Engine on Ubuntu
Distribution Registry
Private Docker registry with HTTPS and a Nginx reverse proxy using Docker Compose

前言

一般情况下,日常开发我只使用vagrant跟virtualbox,但后面想搭建一个k8s环境学习,需要一个免费、高性能、便于管理的虚拟机管理软件

综合考虑了下现在市面上的虚拟机管理软件

  1. virtualbox 的磁盘性能太差,nat网络所有虚拟机都是同一个IP:10.0.2.15,其他方面也不考虑了
  2. parallel desktop 太贵太黑心
  3. vmware 价格也不便宜还很吃电脑资源
  4. hyper-v 管理界面太难用,同时无法固定虚拟机IP

最终我选择组装一台台式机,安装了ubuntu系统,并在上面安装使用kvm虚拟机

cpu是amd 7700,8核心16线程;32GB内存;暂时不需要用到显卡;PC在路由器做了MAC跟IP的绑定,方便使用ssh

由于virt-manager太难用,最后选择cockpit作为kvm虚拟机管理软件

kvm安装

前置工作

确认cpu开启虚拟化功能

1
2
# 一般是cpu的核心数量
egrep -c '(vmx|svm)' /proc/cpuinfo

安装kvm

安装kvm以及依赖软件

1
2
3
4
5
6
7
8
9
10
11
12
sudo apt install qemu-system-x86 libvirt-daemon-system virtinst \
virt-manager virt-viewer ovmf swtpm qemu-utils guestfs-tools \
libosinfo-bin tuned

# 开机启动
sudo systemctl enable libvirtd

# 验证安装
sudo virt-host-validate qemu

# 如果有使用window的需要,可以下载virtio
wget https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.240-1/virtio-win-0.1.240.iso

安装后工作

将用户添加到kvm跟libvirt组

1
2
sudo usermod -aG kvm $USER
sudo usermod -aG libvirt $USER

cockpit安装

1
sudo apt install cockpit cockpit-machines

防火墙配置

1
2
sudo ufw allow 9090/tcp
sudo ufw reload

浏览器打开访问web管理页面 https://192.168.0.105:9090

kvm with cockpit preview

参考文档

How to Install QEMU/KVM on Ubuntu to Create Virtual Machines
Managing KVM Virtual Machines with Cockpit Web Console in Linux
gpu-passthrough-tutorial

0%