oz1010's blog

记录生活或技能

Docker容器安装

镜像源切换

CentOS7主机

软件源切换阿里开源镜像站

1
2
3
4
5
6
7
8
9
# 源备份
mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/bk.CentOS-Base.repo

# 下载镜像源
curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo

# 升级系统相关组件
yum update
yum upgrade

容器本体安装

CentOS7主机

参考:菜鸟教程-CentOS Docker安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 安装容器基本依赖
sudo yum install -y yum-utils \
device-mapper-persistent-data \
lvm2
sudo yum-config-manager \
--add-repo \
https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/docker-ce.repo

# 安装特定版本容器,19.03.15
yum list docker-ce --showduplicates | sort -r
VERSION_STRING=19.03.15; sudo yum install -y docker-ce-${VERSION_STRING} docker-ce-cli-${VERSION_STRING} containerd.io

# 启动服务
systemctl start docker && systemctl enable docker

容器配置

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
# /home/imsdata/docker是Docker基本路径,镜像和容器都会占用此空间,根据需要改动
# https://hub-mirror.c.163.com
# https://reg-mirror.qiniu.com
mkdir -p /etc/docker && mkdir -p /mnt/data_20/docker-root && cat > /etc/docker/daemon.json <<-EOF
{
"registry-mirrors": [
"http://docker.mirrors.ustc.edu.cn",
"http://registry.docker-cn.com",
"http://hub-mirror.c.163.com"
],
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
},
"data-root": "$_"
}
EOF
# 老版本为graph
# 重启容器生效
systemctl daemon-reload && systemctl restart docker
# 容器命令验证,Docker Root Dir: 得到配置的路径
# Cgroup Driver: systemd
docker info | grep -E "Docker Root Dir:|Cgroup Driver:"

安装前准备

设置主机名

1
2
3
4
5
6
7
# 设置主机名
hostnamectl set-hostname <HOSTNAME>

# 将所有主机名加入hosts中,修改/etc/hosts
# echo -ne "192.168.11.251 node1\n192.168.11.252 node2\n192.168.11.253 node3\n" >> /etc/hosts
# echo -ne "n1 192.168.58.12\nn2 192.168.58.14\n" >> /etc/hosts
echo <IP> <HOSTNAME> >> /etc/hosts

禁用防火墙和缓存

CentOS主机

以下步骤二选一,若跳过禁用防火墙,需要增加端口规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 禁用防火墙
systemctl stop firewalld && systemctl disable firewalld

# 开启防火墙后,需要打开NAT转发
# 否则k8s正常启动后,dns解析失败
firewall-cmd --permanent --add-masquerade
# 开放端口规则
# 必须开放k8s使用的6443 10250
firewall-cmd --permanent --zone=public --add-port=6443/tcp
firewall-cmd --permanent --zone=public --add-port=10250/tcp
# 删除端口规则
# firewall-cmd --permanent --zone=public --remove-port=6443/tcp
# 开放指定范围端口规则
firewall-cmd --permanent --zone=public --add-port=31080-31090/tcp
# 开发虚拟网卡
firewall-cmd --permanent --zone=public --add-interface=cni0
# 让规则生效
firewall-cmd --reload
# 查看所有规则
firewall-cmd --list-all

禁用缓存:

1
2
3
4
5
6
7
8
9
10
11
12
# 禁用swap
# 注释/etc/fstab中swap行
swapoff -a
# 注释swap相关行 /mnt/swap swap swap defaults 0 0 这一行或者注释掉这一行
# 虚拟机中运行
# sed -i 's%^/dev/mapper/centos-swap.%# swap %g' /etc/fstab
sed -i 's/\(^[^#|.]*swap.*swap.*\)/#\1/g' /etc/fstab

# 修改启动等待时间为1s
# vim /boot/grub2/grub.cfg,修改第一个timeout值为0,跳过开机等待
# sed -i 's/^ set timeout=.*/ set timeout=0/' /boot/grub2/grub.cfg
sed -i 's/\(^\s*set timeout=\).*/\13/' /boot/grub2/grub.cfg

关闭SELinux

CentOS主机

永久方法 – 需要重启服务器

修改 /etc/selinux/config 文件中设置 SELINUX=disabled ,然后重启服务器,命令

1
2
3
#sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config
sed -i 's/\(^SELINUX=\).*/\1disabled/g' /etc/selinux/config
setenforce 0

打开iptables的支持

Debian10通过命令sysctl -a查看,默认已经打开iptabels支持。

CentOS主机

1
2
3
4
5
6
7
8
9
10
11
12
# 配置内核参数(虚拟机方式)
echo -ne "net.bridge.bridge-nf-call-iptables = 1\nnet.bridge.bridge-nf-call-ip6tables = 1\n" >> /etc/sysctl.conf
# 内核配置生效
sysctl -p

# 显示内核参数,回显1为打开,0为关闭
sysctl -a | egrep "bridge-nf-call-iptables|bridge-nf-call-ip6tables|ip_forward"
# 另一种查看方法
cat /proc/sys/net/bridge/bridge-nf-call-iptables
cat /proc/sys/net/bridge/bridge-nf-call-ip6tables
cat /proc/sys/net/ipv4/ip_forward

配置国内源

若有vpn可以跳过,参考:阿里开源-Kubernetes镜像

CentOS主机

1
2
3
4
5
6
7
8
9
10
11
# 配置国内源
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
sudo yum update

若出现签名验证不过signature could not be verified for kubernetes,可以强行跳过签名验证:

1
2
3
4
5
6
7
8
9
10
11
# 配置国内源
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
sudo yum update

安装辅助工具

安装特定版本辅助工具,例如:1.21.14

CentOS主机

1
2
3
4
5
6
7
8
# 安装特定版本的工具
yum list kubelet --showduplicates | sort -r
# KUBE_VERSION=1.21.11; yum install -y kubelet-${KUBE_VERSION} kubeadm-${KUBE_VERSION} kubectl-${KUBE_VERSION}
KUBE_VERSION=1.21.14; yum install -y kubelet-${KUBE_VERSION} kubeadm-${KUBE_VERSION} kubectl-${KUBE_VERSION}
# 启动kubelet
systemctl enable kubelet && systemctl start kubelet
# 此时因k8s配置文件不存在,kubelet启动失败
systemctl status kubelet

安装K8s

拉取镜像

从国内源在线拉取

1
2
3
4
5
6
7
8
9
10
# 列出所有需要的版本镜像
kubeadm config images list --image-repository registry.cn-hangzhou.aliyuncs.com/google_containers

# 拉取所有需要的版本镜像
kubeadm config images pull --image-repository registry.cn-hangzhou.aliyuncs.com/google_containers

# 测试域名 docker pull busybox:1.28.4
# 网络控制器 docker pull quay.io/coreos/flannel:v0.14.0
# ingress类控制器 docker pull quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.29.0
# 集群管理 docker pull swr.cn-east-2.myhuaweicloud.com/kuboard/kuboard:v3

若已下载镜像执行

1
2
ls k8s-images-1.21.9/*.image | while read file; do docker image load -i $file; done
ls flannel-images/*.image | while read file; do docker image load -i $file; done

【离线安装】批量镜像处理——非必须

1
2
3
4
5
# 批量保存镜像
docker image ls | grep "registry.cn-hangzhou.aliyuncs.com" | awk '{print $1 ":" $2}' | while read image; do file=${image//\//_}.image; file=${file//:/_}; docker image save $image -o $file; done

# 批量加载镜像
ls *.image | while read file; do docker image load -i $file; done

初始化K8s

从节点无需初始化

1
2
3
4
5
6
7
8
9
10
11
12
# 指定镜像源初始化
# 需要特定网络,需要在此步骤中指定
# 使用flannel网络需要指定--pod-network-cidr参数
# --pod-network-cidr 指定pod网络地址范围
# --apiserver-advertise-address 指定集群API server绑定地址
# --service-dns-domain 指定各服务顶级dns域名
kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=172.20.1.25 --image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers --service-dns-domain=ice | tee ./kubeadm-init.log
#kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=192.168.8.101 --image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers --service-dns-domain=viid

# 出现Your Kubernetes control-plane has initialized successfully!表明初始化正常
# 服务状态正常,但因未配置网络/etc/cni/net.d,后台上报异常
systemctl status kubelet

若有cgroup改systemd告警,处理参考

安装命令补全工具

参考后面K8s配置章节

启动集群

1
2
3
4
5
6
7
8
9
10
11
12
# 启动集群
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

# 若是root用户,需要执行
export KUBECONFIG=/etc/kubernetes/admin.conf

# 允许Master参与调度,单节点必须将Master置为可调度状态
kubectl get nodes
kubectl taint node localhost.localdomain node-role.kubernetes.io/master- #将 Master 也当作 Node 使用
kubectl taint node localhost.localdomain node-role.kubernetes.io/master=:NoSchedule #将 Master 恢复成 Master Only 状态

路由选择

默认kube-proxy使用iptables实现,随着service、pod数量增加,iptables顺序遍历的方式就会凸显。可替代方案可以选择:

  • iptables
  • IPVS

kube-proxy支持的另一种模式,性能比iptables更高,推荐使用。参考文章

  • eBPF

整体成熟度低,不建议单独使用,参考文章

替换kube-proxy方法,参考文章

可以对数据包进行观测,参考文章

网络配置

上面安装成功后如果通过查询kube-system下Pod的运行情况,会放下和网络相关的Pod都处于Pending的状态,这是因为缺少相关的网络插件,而网络插件有很多个(以下任选一个),可以选择自己需要的。参考:Kubernetes指南

flannel

参考官网

官方网站,在Documentation文件夹中,参考kube-flannel.yaml, kube-flannel-aliyun.yaml, kube-flannel-old.yaml描述文件

kube-flannel.yaml文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
---
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: psp.flannel.unprivileged
annotations:
seccomp.security.alpha.kubernetes.io/allowedProfileNames: docker/default
seccomp.security.alpha.kubernetes.io/defaultProfileName: docker/default
apparmor.security.beta.kubernetes.io/allowedProfileNames: runtime/default
apparmor.security.beta.kubernetes.io/defaultProfileName: runtime/default
spec:
privileged: false
volumes:
- configMap
- secret
- emptyDir
- hostPath
allowedHostPaths:
- pathPrefix: "/etc/cni/net.d"
- pathPrefix: "/etc/kube-flannel"
- pathPrefix: "/run/flannel"
readOnlyRootFilesystem: false
# Users and groups
runAsUser:
rule: RunAsAny
supplementalGroups:
rule: RunAsAny
fsGroup:
rule: RunAsAny
# Privilege Escalation
allowPrivilegeEscalation: false
defaultAllowPrivilegeEscalation: false
# Capabilities
allowedCapabilities: ['NET_ADMIN', 'NET_RAW']
defaultAddCapabilities: []
requiredDropCapabilities: []
# Host namespaces
hostPID: false
hostIPC: false
hostNetwork: true
hostPorts:
- min: 0
max: 65535
# SELinux
seLinux:
# SELinux is unused in CaaSP
rule: 'RunAsAny'
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: flannel
rules:
- apiGroups: ['extensions']
resources: ['podsecuritypolicies']
verbs: ['use']
resourceNames: ['psp.flannel.unprivileged']
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- apiGroups:
- ""
resources:
- nodes
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- nodes/status
verbs:
- patch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: flannel
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: flannel
subjects:
- kind: ServiceAccount
name: flannel
namespace: kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: flannel
namespace: kube-system
---
kind: ConfigMap
apiVersion: v1
metadata:
name: kube-flannel-cfg
namespace: kube-system
labels:
tier: node
app: flannel
data:
cni-conf.json: |
{
"name": "cbr0",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "host-gw"
}
}
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-flannel-ds
namespace: kube-system
labels:
tier: node
app: flannel
spec:
selector:
matchLabels:
app: flannel
template:
metadata:
labels:
tier: node
app: flannel
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
hostNetwork: true
priorityClassName: system-node-critical
tolerations:
- operator: Exists
effect: NoSchedule
serviceAccountName: flannel
initContainers:
- name: install-cni
image: quay.io/coreos/flannel:v0.14.0
command:
- cp
args:
- -f
- /etc/kube-flannel/cni-conf.json
- /etc/cni/net.d/10-flannel.conflist
volumeMounts:
- name: cni
mountPath: /etc/cni/net.d
- name: flannel-cfg
mountPath: /etc/kube-flannel/
containers:
- name: kube-flannel
image: quay.io/coreos/flannel:v0.14.0
command:
- /opt/bin/flanneld
args:
- --ip-masq
- --kube-subnet-mgr
resources:
requests:
cpu: "100m"
memory: "50Mi"
limits:
cpu: "100m"
memory: "50Mi"
securityContext:
privileged: false
capabilities:
add: ["NET_ADMIN", "NET_RAW"]
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
volumeMounts:
- name: run
mountPath: /run/flannel
- name: flannel-cfg
mountPath: /etc/kube-flannel/
volumes:
- name: run
hostPath:
path: /run/flannel
- name: cni
hostPath:
path: /etc/cni/net.d
- name: flannel-cfg
configMap:
name: kube-flannel-cfg

Network.Network: 10.244.0.0/16需要改成kubeadm初始化参数所带网络参数。

Network.Backend.Type: vxlan为默认网络参数,已经改为host-gw测试文档中发现host-gw模式性能更高。

创建网络:

1
kubectl apply -f kube-flannel.yaml

等待片刻可以看到虚拟网桥cni0已经创建好。若coredns一直处于pending状态,请将flannel文件改为最新版本(0.19.2)

查看系统空间kube-systemflannel网络pod资源已经正常运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
kubectl -n kube-system get all

# NAME READY STATUS RESTARTS AGE
# pod/coredns-6f6b8cc4f6-95bng 1/1 Running 0 24m
# pod/coredns-6f6b8cc4f6-hkdn8 1/1 Running 0 24m
# pod/etcd-n1 1/1 Running 1 24m
# pod/kube-apiserver-n1 1/1 Running 1 24m
# pod/kube-controller-manager-n1 1/1 Running 1 24m
# pod/kube-flannel-ds-vz49s 1/1 Running 0 74s
# pod/kube-proxy-bbjzq 1/1 Running 1 24m
# pod/kube-scheduler-n1 1/1 Running 1 24m
#
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# service/kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 24m
#
# NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
# daemonset.apps/kube-flannel-ds 1 1 1 1 1 <none> 74s
# daemonset.apps/kube-proxy 1 1 1 1 1 kubernetes.io/os=linux 24m
#
# NAME READY UP-TO-DATE AVAILABLE AGE
# deployment.apps/coredns 2/2 2 2 24m
#
# NAME DESIRED CURRENT READY AGE
# replicaset.apps/coredns-6f6b8cc4f6 2 2 2 24m

cni

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
mkdir -p /etc/cni/net.d
cat >/etc/cni/net.d/10-mynet.conf <<-EOF
{
"cniVersion": "0.3.0",
"name": "mynet",
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.244.0.0/16",
"routes": [
{"dst": "0.0.0.0/0"}
]
}
}
EOF
cat >/etc/cni/net.d/99-loopback.conf <<-EOF
{
"cniVersion": "0.3.0",
"type": "loopback"
}
EOF

等待片刻可以看到虚拟网桥cni0已经创建好。

查看是否安装成功

1
2
3
4
5
6
7
8
9
10
11
12
# 查看系统pod是否正常启动
kubectl get pods -n kube-system
# 看到类似结果
#NAME READY STATUS RESTARTS AGE
#coredns-6f6b8cc4f6-285bg 1/1 Running 0 7m38s
#coredns-6f6b8cc4f6-znlf7 1/1 Running 0 7m38s
#etcd-n175 1/1 Running 0 7m46s
#kube-apiserver-n175 1/1 Running 0 7m46s
#kube-controller-manager-n175 1/1 Running 0 7m46s
#kube-flannel-ds-8p8xx 1/1 Running 0 95s
#kube-proxy-5zvr2 1/1 Running 0 7m38s
#kube-scheduler-n175 1/1 Running 0 7m46s

域名解析测试方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 启动容器,-n xxx可以指定创建的命名空间
kubectl run -it --rm --image=busybox:1.28.4 --restart=Never sh

# 解析域名地址,格式:<Service>.<Namespace>.svc.cluster.local
nslookup kube-dns.kube-system
nslookup kube-dns.kube-system.svc.cluster.ice
nslookup ver-svc
nslookup ver-svc.ver-dev
nslookup ver-svc.ver-dev.svc
nslookup ver-svc.ver-dev.svc.cluster
nslookup ver-svc.ver-dev.svc.cluster.ice
# 正常能查看到类似结果
#Server: 10.96.0.10
#Address 1: 10.96.0.10 kube-dns.kube-system.svc.ice
#
#Name: kube-dns.kube-system
#Address 1: 10.96.0.10 kube-dns.kube-system.svc.ice

域名解析时都会优先尝试直接域名解析,若无法连通则会逐步扩大解析范围。使用简短域名解析通过tcpdump -i cni0 -w result.cap抓包dns流程分析,简短域名解析耗时10+ms,而完整域名解析1+ms,因此推荐使用完整域名解析。

工作Node节点加入集群

操作步骤到安装辅助工具后,只需要加载pause、proxy、网络插件(若有)镜像。再运行主节点部署时的回显命令即可加入集群。

1
2
3
# 加载节点镜像
docker image load -i k8s-images-1.21.8/registry.cn-hangzhou.aliyuncs.com_google_containers_pause_3.4.1.image
docker image load -i k8s-images-1.21.8/registry.cn-hangzhou.aliyuncs.com_google_containers_kube-proxy_v1.21.8.image

Ingress类控制器安装

ingress-nginx

官方GitHub仓,注意官方使用的镜像k8s.gcr.io可能无法下载,需要替换一下

nginx分支

参考文章

nginx-0.29.0分支为例,进入到资源描述路径deploy/static

  • configmap.yaml 提供configmap可以在线更新nginx的配置
  • namespace.yaml 创建一个独立的命名空间 ingress-nginx
  • rbac.yaml 创建对应的role rolebinding 用于rbac
  • with-rbac.yaml 有应用rbac的nginx-ingress-controller组件
  • mandatory.yaml 以上所有文件的集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
apiVersion: v1
kind: Namespace
metadata:
name: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx

---

kind: ConfigMap
apiVersion: v1
metadata:
name: nginx-configuration
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx

---
kind: ConfigMap
apiVersion: v1
metadata:
name: tcp-services
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx

---
kind: ConfigMap
apiVersion: v1
metadata:
name: udp-services
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx

---
apiVersion: v1
kind: ServiceAccount
metadata:
name: nginx-ingress-serviceaccount
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: nginx-ingress-clusterrole
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
rules:
- apiGroups:
- ""
resources:
- configmaps
- endpoints
- nodes
- pods
- secrets
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- apiGroups:
- ""
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- "extensions"
- "networking.k8s.io"
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- "extensions"
- "networking.k8s.io"
resources:
- ingresses/status
verbs:
- update

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
name: nginx-ingress-role
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
rules:
- apiGroups:
- ""
resources:
- configmaps
- pods
- secrets
- namespaces
verbs:
- get
- apiGroups:
- ""
resources:
- configmaps
resourceNames:
# Defaults to "<election-id>-<ingress-class>"
# Here: "<ingress-controller-leader>-<nginx>"
# This has to be adapted if you change either parameter
# when launching the nginx-ingress-controller.
- "ingress-controller-leader-nginx"
verbs:
- get
- update
- apiGroups:
- ""
resources:
- configmaps
verbs:
- create
- apiGroups:
- ""
resources:
- endpoints
verbs:
- get

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
name: nginx-ingress-role-nisa-binding
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: nginx-ingress-role
subjects:
- kind: ServiceAccount
name: nginx-ingress-serviceaccount
namespace: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: nginx-ingress-clusterrole-nisa-binding
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: nginx-ingress-clusterrole
subjects:
- kind: ServiceAccount
name: nginx-ingress-serviceaccount
namespace: ingress-nginx

---

apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-ingress-controller
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
template:
metadata:
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
annotations:
prometheus.io/port: "10254"
prometheus.io/scrape: "true"
spec:
hostNetwork: true # 设置物理网络
# wait up to five minutes for the drain of connections
terminationGracePeriodSeconds: 300
serviceAccountName: nginx-ingress-serviceaccount
nodeSelector:
kubernetes.io/os: linux
containers:
- name: nginx-ingress-controller
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.29.0
args:
- /nginx-ingress-controller
- --configmap=$(POD_NAMESPACE)/nginx-configuration
- --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
- --udp-services-configmap=$(POD_NAMESPACE)/udp-services
- --publish-service=$(POD_NAMESPACE)/ingress-nginx
- --annotations-prefix=nginx.ingress.kubernetes.io
# 增加额外参数指定暴露端口
- --http-port=31080
- --https-port=31443
securityContext:
allowPrivilegeEscalation: true
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
# www-data -> 101
runAsUser: 101
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- name: http
containerPort: 31080
protocol: TCP
hostPort: 31080 # 指定物理映射端口,可添加也可不添加
- name: https
containerPort: 31443
protocol: TCP
hostPort: 31443 # 指定物理映射端口,可添加也可不添加
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
lifecycle:
preStop:
exec:
command:
- /wait-shutdown

---

apiVersion: v1
kind: LimitRange
metadata:
name: ingress-nginx
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
limits:
- min:
memory: 90Mi
cpu: 100m
type: Container

svc服务文件deploy/baremetal/service-nodeport.yaml,将ingress内部80端口暴露到节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx
namespace: ingress-nginx
spec:
type: ClusterIP
#type: NodePort
ports:
- name: http
port: 31080
#nodePort: 31080
targetPort: 31080
protocol: TCP
- name: https
port: 31443
targetPort: 31443
protocol: TCP
selector:
app: ingress-nginx
#externalTrafficPolicy: Cluster

默认nginx-ingress-controller会随意选择一个node节点运行pod,为此需要我们把nginx-ingress-controller运行到指定的node节点上。首先需要给需要运行nginx-ingress-controller的node节点打标签,在此我们把nginx-ingress-controller运行在指定的node节点上

此步骤非必须

为节点打标签

1
2
3
4
5
# 为指定节点打标签,标签可以换成其他的
kubectl label node localhost.localdomain nodeFeature=nginx

# 查看节点已有标签
kubectl get nodes --show-labels

mandatory.yaml文件中nodeSelector属性中增加nodeFeature: nginx

验证程序

ingress-nginx测试程序httpd-dep.yaml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: httpd
labels:
name: httpd
spec:
rules:
- http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: httpd
port:
number: 8000

---
apiVersion: v1
kind: Service
metadata:
name: httpd
spec:
selector:
app: httpd
ports:
- port: 8000
protocol: TCP
targetPort: 80
type: ClusterIP

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpd
spec:
replicas: 4
selector:
matchLabels:
app: httpd
template:
metadata:
labels:
app: httpd
spec:
containers:
- name: httpd
image: httpd:2.4.52
resources:
limits:
memory: "128Mi"
cpu: "500m"
ports:
- containerPort: 80

若未指定ingress会随机选择节点启动pod,查看命令

1
2
3
4
kubectl -n ingress-nginx get pod -o wide

#NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
#nginx-ingress-controller-fd46d8644-s772j 1/1 Running 0 103m 192.168.31.192 n2 <none> <none>

K8s上部署Redis集群

【重点】不适合部署带持久化的Redis集群,因Redis集群以IP建立的。

重启后无法恢复原有集群。

本方案采用StatefulSet进行redis的部署。参考文章

环境信息

序列节点IP
1master192.168.1.100
2node1192.168.1.101
3node2192.168.1.102
4node3192.168.1.103

创建存储卷

  1. 安装nfs软件包
1
2
#任选一台节点(这里选k8s-master)
yum –y install nfs-utils rpcbind
  1. 创建共享存储
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 创建共享目录
mkdir -p /home/data/redis/pv{1,2,3,4,5,6}

# 配置共享路径
vi /etc/exports
# 增加如下配置
/home/data/redis/pv1 192.168.11.0/24(rw,sync,no_root_squash)
/home/data/redis/pv2 192.168.11.0/24(rw,sync,no_root_squash)
/home/data/redis/pv3 192.168.11.0/24(rw,sync,no_root_squash)
/home/data/redis/pv4 192.168.11.0/24(rw,sync,no_root_squash)
/home/data/redis/pv5 192.168.11.0/24(rw,sync,no_root_squash)
/home/data/redis/pv6 192.168.11.0/24(rw,sync,no_root_squash)

# 重启
systemctl restart rpcbind
systemctl restart nfs
systemctl enable nfs

# 其他节点验证nfs
yum -y install nfs-utils
showmount -e 172.20.1.25

创建PV

pv.yaml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv1
spec:
capacity:
storage: 2Gi
accessModes:
- ReadWriteMany
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Recycle
storageClassName: "redis"
nfs:
server: 192.168.1.10
path: "/usr/local/k8s/redis/pv1"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-vp2
spec:
capacity:
storage: 2Gi
accessModes:
- ReadWriteMany
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Recycle
storageClassName: "redis"
nfs:
server: 192.168.1.10
path: "/usr/local/k8s/redis/pv2"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv3
spec:
capacity:
storage: 2Gi
accessModes:
- ReadWriteMany
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Recycle
storageClassName: "redis"
nfs:
server: 192.168.1.10
path: "/usr/local/k8s/redis/pv3"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-vp4
spec:
capacity:
storage: 2Gi
accessModes:
- ReadWriteMany
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Recycle
storageClassName: "redis"
nfs:
server: 192.168.1.10
path: "/usr/local/k8s/redis/pv4"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv5
spec:
capacity:
storage: 2Gi
accessModes:
- ReadWriteMany
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Recycle
storageClassName: "redis"
nfs:
server: 192.168.1.10
path: "/usr/local/k8s/redis/pv5"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-vp6
spec:
capacity:
storage: 2Gi
accessModes:
- ReadWriteMany
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Recycle
storageClassName: "redis"
nfs:
server: 192.168.1.10
path: "/usr/local/k8s/redis/pv6"

创建configmap

vim redis.conf

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
# appendonly yes
# cluster-enabled yes
# cluster-config-file /var/lib/redis/nodes.conf
# cluster-node-timeout 5000
# dir /var/lib/redis
# port 6379
# redis端口
port 6379
# # 连接密码
# requirepass hzzhcs@2020
# masterauth hzzhcs@2020
# 关闭保护模式
protected-mode no
# 开启集群
cluster-enabled yes
# 集群节点配置
cluster-config-file nodes-${PORT}.conf
# 超时
cluster-node-timeout 5000
# # 集群节点IP host模式为宿主机IP
# # cluster-announce-ip 192.168.195.10
# # cluster-announce-ip 192.168.28.170
# cluster-announce-ip 192.168.11.215
# # 节点端口 6379 - 6381
# # 集群端口 16379 - 16381
# cluster-announce-port ${PORT}
# cluster-announce-bus-port ${CPORT}
# 开启 appendonly 备份模式
appendonly yes
# 每秒钟备份
appendfsync everysec
# 对aof文件进行压缩时,是否执行同步操作
no-appendfsync-on-rewrite no
# 当目前aof文件大小超过上一次重写时的aof文件大小的100%时会再次进行重写
auto-aof-rewrite-percentage 100
# 重写前AOF文件的大小最小值 默认 64mb
auto-aof-rewrite-min-size 64mb

创建

1
kubectl create configmap redis-conf --from-file=redis.conf

创建headless service

vim headless-service.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
---
apiVersion: v1
kind: Service
metadata:
name: redis-service
labels:
app: redis
spec:
ports:
- name: redis-port
port: 6379
clusterIP: None
selector:
app: redis

创建redis集群节点

通过StatefulSet创建6个redis的pod ,实现3主3从的redis集群。vim redis.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis-app
spec:
serviceName: "redis-service"
replicas: 6
selector:
matchLabels:
app: redis
appCluster: redis-cluster
template:
metadata:
labels:
app: redis
appCluster: redis-cluster
spec:
containers:
- name: redis
image: "redis:3.2.8"
command:
- "redis-server"
args:
- "/etc/redis/redis.conf"
- "-protected-mode"
- "no"
resources:
requests:
cpu: "100m"
memory: "100Mi"
ports:
- name: redis
containerPort: 6379
protocol: "TCP"
- name: cluster
containerPort: 16379
protocol: "TCP"
volumeMounts:
- name: "redis-conf"
mountPath: "/etc/redis"
- name: "redis-data"
mountPath: "/var/lib/redis"
volumes:
- name: "redis-conf"
configMap:
name: "redis-conf"
items:
- key: "redis.conf"
path: "redis.conf"
volumeClaimTemplates:
- metadata:
name: redis-data
spec:
accessModes: [ "ReadWriteMany" ]
storageClassName: "redispv"
resources:
requests:
storage: 2Gi

初始化redis集群

获取节点信息

1
2
3
4
5
6
7
8
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
redis-app-0 1/1 Running 0 2m22s 10.244.0.248 n175 <none> <none>
redis-app-1 1/1 Running 0 97s 10.244.0.252 n175 <none> <none>
redis-app-2 1/1 Running 0 101s 10.244.0.251 n175 <none> <none>
redis-app-3 1/1 Running 0 117s 10.244.0.250 n175 <none> <none>
redis-app-4 1/1 Running 0 2m1s 10.244.0.249 n175 <none> <none>
redis-app-5 1/1 Running 0 2m31s 10.244.0.247 n175 <none> <none>

进入任意节点执行命令初始化集群

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
redis-cli --cluster create \
10.244.0.248:6379 \
10.244.0.252:6379 \
10.244.0.251:6379 \
10.244.0.250:6379 \
10.244.0.249:6379 \
10.244.0.247:6379 \
--cluster-replicas 1

# 无法正常建立集群
redis-cli --cluster create \
redis-app-0.redis-service.default.svc.ice:6379 \
redis-app-1.redis-service.default.svc.ice:6379 \
redis-app-2.redis-service.default.svc.ice:6379 \
redis-app-3.redis-service.default.svc.ice:6379 \
redis-app-4.redis-service.default.svc.ice:6379 \
redis-app-5.redis-service.default.svc.ice:6379 \
--cluster-replicas 1

# 回显信息
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 10.244.0.249:6379 to 10.244.0.248:6379
Adding replica 10.244.0.247:6379 to 10.244.0.252:6379
Adding replica 10.244.0.250:6379 to 10.244.0.251:6379
M: 8921255530faaa44fb793a7248d93c179211b7d9 10.244.0.248:6379
slots:[0-5460] (5461 slots) master
M: f32bcfa4dcacef662096d5ccdef4d741588aa2cb 10.244.0.252:6379
slots:[5461-10922] (5462 slots) master
M: dc0a1908830468f2070883e1c026fd5b1b2ff526 10.244.0.251:6379
slots:[10923-16383] (5461 slots) master
S: b35dcfba64b30050d3f71dc347acd3ce222a99e5 10.244.0.250:6379
replicates dc0a1908830468f2070883e1c026fd5b1b2ff526
S: 4ac41eded080c70132ab4de211fcdfa653874469 10.244.0.249:6379
replicates 8921255530faaa44fb793a7248d93c179211b7d9
S: dc2ddbb2ef518a23583dab53bdecccc0e017146d 10.244.0.247:6379
replicates f32bcfa4dcacef662096d5ccdef4d741588aa2cb
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
..
>>> Performing Cluster Check (using node 10.244.0.248:6379)
M: 8921255530faaa44fb793a7248d93c179211b7d9 10.244.0.248:6379
slots:[0-5460] (5461 slots) master
1 additional replica(s)
S: 4ac41eded080c70132ab4de211fcdfa653874469 10.244.0.249:6379
slots: (0 slots) slave
replicates 8921255530faaa44fb793a7248d93c179211b7d9
M: f32bcfa4dcacef662096d5ccdef4d741588aa2cb 10.244.0.252:6379
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: b35dcfba64b30050d3f71dc347acd3ce222a99e5 10.244.0.250:6379
slots: (0 slots) slave
replicates dc0a1908830468f2070883e1c026fd5b1b2ff526
S: dc2ddbb2ef518a23583dab53bdecccc0e017146d 10.244.0.247:6379
slots: (0 slots) slave
replicates f32bcfa4dcacef662096d5ccdef4d741588aa2cb
M: dc0a1908830468f2070883e1c026fd5b1b2ff526 10.244.0.251:6379
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

验证状态

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
redis-cli -c

# 操作回显信息
127.0.0.1:6379> CLUSTER INFO
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:315
cluster_stats_messages_pong_sent:335
cluster_stats_messages_sent:650
cluster_stats_messages_ping_received:330
cluster_stats_messages_pong_received:315
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:650
127.0.0.1:6379> CLUSTER NODES
4ac41eded080c70132ab4de211fcdfa653874469 10.244.0.249:6379@16379 slave 8921255530faaa44fb793a7248d93c179211b7d9 0 1658228657078 1 connected
f32bcfa4dcacef662096d5ccdef4d741588aa2cb 10.244.0.252:6379@16379 master - 0 1658228657580 2 connected 5461-10922
b35dcfba64b30050d3f71dc347acd3ce222a99e5 10.244.0.250:6379@16379 slave dc0a1908830468f2070883e1c026fd5b1b2ff526 0 1658228658082 3 connected
dc2ddbb2ef518a23583dab53bdecccc0e017146d 10.244.0.247:6379@16379 slave f32bcfa4dcacef662096d5ccdef4d741588aa2cb 0 1658228657000 2 connected
8921255530faaa44fb793a7248d93c179211b7d9 10.244.0.248:6379@16379 myself,master - 0 1658228657000 1 connected 0-5460
dc0a1908830468f2070883e1c026fd5b1b2ff526 10.244.0.251:6379@16379 master - 0 1658228657078 3 connected 10923-16383

创建用于访问的service

之前创建了用于实现StatefulSet的Headless Service,但该Service没有Cluster IP,因此不能用于外界访问。所以,我们还需要创建一个Service,专用于为Redis集群提供访问和负载均衡;也可以部署为Ingress供集群外部访问。这里只创建用于内部访问的service

redis-access-service.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Service
metadata:
name: redis-access-service
labels:
app: redis
spec:
ports:
- name: redis-port
protocol: "TCP"
port: 6379
targetPort: 6379
selector:
app: redis
appCluster: redis-cluster

K8s上部署Kafka集群

K8s上部署MySQL集群

Helm离线安装Chart包

参考文章

CICD引擎(非必须)

Jenkins替代工具

BuildMaster Drone.io GoCD

Argo

可视化管理工具(非必须)

kuboard

在线安装

1
2
3
4
5
6
7
8
9
10
11
12
  # 也可以使用镜像 swr.cn-east-2.myhuaweicloud.com/kuboard/kuboard:v3 ,可以更快地完成镜像下载。
# 请不要使用 127.0.0.1 或者 localhost 作为内网 IP \
# Kuboard 不需要和 K8S 在同一个网段,Kuboard Agent 甚至可以通过代理访问 Kuboard Server \
sudo docker run -d \
--restart=unless-stopped \
--name=kuboard \
-p 30080:80/tcp \
-p 31089:10081/tcp \
-e KUBOARD_ENDPOINT="http://192.168.11.178:30080" \
-e KUBOARD_AGENT_SERVER_TCP_PORT="31089" \
-v /opt/kuboard-data:/data \
swr.cn-east-2.myhuaweicloud.com/kuboard/kuboard:v3.4.1.0

docker-compose.yml方式:

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
version: "3"

networks:
kuboard-net:
external: false
driver: bridge
# ipam:
# config:
# - subnet: 172.90.161.0/24

services:
kuboard:
image: swr.cn-east-2.myhuaweicloud.com/kuboard/kuboard:v3.4.1.0
container_name: kuboard
restart: unless-stopped # always
environment:
# user/passwd:admin/Kuboard123
# modify: Zdxf@2021
KUBOARD_ENDPOINT: "http://172.20.1.25:30080"
KUBOARD_AGENT_SERVER_TCP_PORT: "31089"
KUBERNETES_CLUSTER_DOMAIN: "ice"
KUBOARD_ICP_DESCRIPTION: "ICP备案号"
KUBOARD_DISABLE_AUDIT: true
ports:
- 30080:80/tcp
- 31089:10081/tcp
volumes:
- /etc/localtime:/etc/localtime:ro
- ./data:/data
networks:
- kuboard-net

logging:
#driver: none
driver: json-file
options:
max-size: "200k"
max-file: "1"

# 使用deploy限制资源,启动时需要增加--compatibility参数,防止报错
deploy:
resources:
limits:
cpus: '1'
memory: 1G
reservations:
cpus: '1'
memory: 200M

在浏览器输入 http://your-host-ip:31088 即可访问 Kuboard v3.x 的界面,登录方式:

  • 用户名: admin
  • 密 码: Kuboard123

浏览器兼容性

请使用 Chrome / FireFox / Safari 等浏览器

不兼容 IE 以及以 IE 为内核的浏览器

资源监控

metrics-server.yaml内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
---
apiVersion: v1
kind: Service
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system
spec:
ports:
- name: https
port: 443
protocol: TCP
targetPort: 443
selector:
k8s-app: metrics-server

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
k8s-app: metrics-server
rbac.authorization.k8s.io/aggregate-to-admin: 'true'
rbac.authorization.k8s.io/aggregate-to-edit: 'true'
rbac.authorization.k8s.io/aggregate-to-view: 'true'
name: 'system:aggregated-metrics-reader'
namespace: kube-system
rules:
- apiGroups:
- metrics.k8s.io
resources:
- pods
- nodes
verbs:
- get
- list
- watch

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
k8s-app: metrics-server
name: 'system:metrics-server'
namespace: kube-system
rules:
- apiGroups:
- ''
resources:
- pods
- nodes
- nodes/stats
- namespaces
- configmaps
verbs:
- get
- list
- watch

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
k8s-app: metrics-server
name: 'metrics-server:system:auth-delegator'
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: 'system:auth-delegator'
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: kube-system

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
k8s-app: metrics-server
name: 'system:metrics-server'
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: 'system:metrics-server'
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: kube-system

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
k8s-app: metrics-server
name: metrics-server-auth-reader
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: extension-apiserver-authentication-reader
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: kube-system

---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system

---
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
labels:
k8s-app: metrics-server
name: v1beta1.metrics.k8s.io
namespace: kube-system
spec:
group: metrics.k8s.io
groupPriorityMinimum: 100
insecureSkipTLSVerify: true
service:
name: metrics-server
namespace: kube-system
version: v1beta1
versionPriority: 100

---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
k8s-app: metrics-server
strategy:
rollingUpdate:
maxUnavailable: 1
template:
metadata:
labels:
k8s-app: metrics-server
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- preference:
matchExpressions:
- key: node-role.kubernetes.io/master
operator: Exists
weight: 100
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
k8s-app: metrics-server
namespaces:
- kube-system
topologyKey: kubernetes.io/hostname
containers:
- args:
- '--cert-dir=/tmp'
- '--secure-port=443'
- '--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname'
- '--kubelet-use-node-status-port'
- '--kubelet-insecure-tls=true'
- '--authorization-always-allow-paths=/livez,/readyz'
- '--metric-resolution=15s'
image: >-
swr.cn-east-2.myhuaweicloud.com/kuboard-dependency/metrics-server:v0.5.0
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 3
httpGet:
path: /livez
port: https
scheme: HTTPS
periodSeconds: 10
name: metrics-server
ports:
- containerPort: 443
name: https
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /readyz
port: https
scheme: HTTPS
initialDelaySeconds: 20
periodSeconds: 10
resources:
requests:
cpu: 100m
memory: 200Mi
securityContext:
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
volumeMounts:
- mountPath: /tmp
name: tmp-dir
nodeSelector:
kubernetes.io/os: linux
priorityClassName: system-cluster-critical
serviceAccountName: metrics-server
tolerations:
- effect: ''
key: node-role.kubernetes.io/master
operator: Exists
volumes:
- emptyDir: {}
name: tmp-dir

---
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: metrics-server
namespace: kube-system
spec:
minAvailable: 1
selector:
matchLabels:
k8s-app: metrics-server

k8sLens

官方网站

K8s配置

删除资源

若资源是DEPLOY.yaml创建的,则可以使用命令

1
2
# 删除yaml文件中描述的资源
kubectl delete -f DEPLOY.yaml

删除命名空间中所有资源–暂时未验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1、先查找该命名空间下的资源有哪些,
kubectl api-resources --verbs=list --namespaced -o name | xargs -n 1 kubectl get --show-kind --ignore-not-found -n ingress-nginx
确定资源类型如下,
ingress
deployment
service

2、清理ingress-nginx命名空间下的资源
kubectl get ingress -n ingress-nginx |grep clife |awk '{print $1}'|xargs kubectl delete ingress -n ingress-nginx
kubectl get service -n ingress-nginx |grep clife |awk '{print $1}'|xargs kubectl delete service -n ingress-nginx
kubectl get deployment -n ingress-nginx |grep clife |awk '{print $1}'|xargs kubectl delete deployment -n ingress-nginx
3、删除命名空间ingress-nginx
kubectl delete ns ingress-nginx
4、查看该命名空间是否已删除
kubectl get ns ingress-nginx

命令补全工具

bash

bash需要安装bash-completion:

1
2
3
yum install bash-completion
echo "source <(kubectl completion bash)" >> ~/.bashrc
source ~/.bashrc

zsh

命令行执行:

1
2
echo "source <(kubectl completion zsh)" >> ~/.zshrc
source ~/.zshrc

普通用户命令权限

命令行执行:

1
2
3
4
5
6
7
# 比如用户名为USER
mkdir -p ~/.kube
sudo cp -i /etc/kubernetes/admin.conf ~/.kube
sudo chown USER:USER /etc/kubernetes/admin.conf
# 配置环境变量
# zsh配置到.zshrc文件中添加
export KUBECONFIG=~/.kube/admin.conf

集群证书

集群证书存放位置/etc/kubernetes/pki/

1
2
3
4
5
# 检查哪些证书过期
kubeadm certs check-expiration

# 手动刷新证书
kubeadm certs renew all

性能测试

工具使用参考测试文档

基础网络工具有:curl,iperf,Locust,kubemark

业务性能测试有:JMeter, LoadRunner

Apache JMeter是压力测试工具。

LoadRunner是一种预测系统行为和性能的负载测试工具。

1
2
3
4
5
6
# 时间指标说明
# 单位:秒
# time_connect:建立到服务器的 TCP 连接所用的时间
# time_starttransfer:在发出请求之后,Web 服务器返回数据的第一个字节所用的时间
# time_total:完成请求所用的时间
curl -o /dev/null -s -w '%{time_connect} %{time_starttransfer} %{time_total}' "http://sample-webapp:8000/"

iperf在CentOS安装方法

1
2
3
yum install epel-release
yum update
yum install iperf

使用方法

1
2
3
4
5
6
7
8
9
10
11
# 启动tcp服务端
iperf -s
# 启动客户端测试
iperf -c <SERVER_IP>

# 启动udp服务端
iperf -s -u
# 启动客户端测试,udp可能受参数限制带宽,可以用-b更改最大带宽
iperf -c <SERVER_IP> -u

# 双向测试只需要客户端增加-d参数

容器化构建发布

构建发布

参考文章,Dockerfile:

1
2
3
4
5
6
7
8
9
10
11
FROM golang:buster as build
WORKDIR /go/src/greeter-server
RUN curl -o main.go https://github.com/grpc/grpc-go/blob/91e0aeb192456225adf27966d04ada4cf8599915/examples/features/reflection/server/main.go && \
go mod init greeter-server && \
go mod tidy && \
go build -o /greeter-server main.go

FROM gcr.io/distroless/base-debian10
COPY --from=build /greeter-server /
EXPOSE 50051
CMD ["/greeter-server"]

grpc示例

ingress支持grpc示例,参考文章

卸载K8s

清理本体

1
2
3
4
# 卸载集群本体
kubeadm reset -f
# 清理本体文件夹
rm -rf ~/.kube/ /etc/kubernetes/ /etc/cni /opt/cni /var/lib/etcd

清理组件

CentOS主机

1
2
# 清理组件
yum autoremove -y kubelet kubeadm kubectl && rm -rf /usr/bin/kube*

Debian/Ubuntu主机

1
2
3
# 清理组件
apt-get remove kube*
rm -rf /usr/bin/kube*

清理相关镜像

1
2
3
4
5
# 由于从registry.cn-hangzhou.aliyuncs.com拉取的镜像,将镜像删除
docker image ls | grep -v grep | grep -v REPOSITORY | grep registry.cn-hangzhou.aliyuncs.com | awk '{print $3}' | xargs docker image rm -

# 将所有镜像全部删除
docker image ls | grep -v REPOSITORY | awk '{print $3}' | xargs docker image rm -

FAQ

bridge-nf-call-iptables异常

安装时报错[ERROR FileContent--proc-sys-net-bridge-bridge-nf-call-iptables]: /proc/sys/net/bridge/bridge-nf-call-iptables contents are not set to 1

参考文章

1
2
3
# 解决方案
echo 1 > /proc/sys/net/bridge/bridge-nf-call-iptables
echo 1 > /proc/sys/net/bridge/bridge-nf-call-ip6tables

flannel无法访问kubernetes资源10.96.0.1不可达

10.96.0.1地址是指向k8s集群default空间创建的kubernetes服务的,底层基础网络不通(tcpdump无法抓到到此IP的包,可以考虑更换工具或者增加参数)。虚拟机使用的是虚拟主机网络,怀疑是网络配置问题,将此网络包丢弃导致。虚拟机更换为普通桥接问题解决。

先参考资料

kube-proxy开启ipvs的前置条件(所有节点)

1
2
3
4
5
6
7
8
9
cat > /etc/sysconfig/modules/ipvs.modules << EOF
#!/bin/bash
modprobe -- ip_vs
modprobe -- ip_vs_rr
modprobe -- ip_vs_wrr
modprobe -- ip_vs_sh
modprobe -- nf_conntrack_ipv4
EOF
chmod 755 /etc/sysconfig/modules/ipvs.modules

docker使用systemd的cgroup

1
2
3
/etc/docker/daemon.json中增加
"exec-opts": ["native.cgroupdriver=systemd"]
Cgroup Driver: cgroupfs

1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
kubeadm config print init-defaults > kubeadm-config.yaml

修改advertiseAddress值为本机IP
修改kubernetesVersion为k8s版本
修改dnsDomain为最高级域名--非必须
新增podSubnet: 10.244.0.0/16容器子网--非必须
修改serviceSubnet服务子网--非必须

# 镜像名需要改--
kubeadm init --config=kubeadm-config.yaml --upload-certs | tee kubeadm-init.log
# 简化初始化流程
kubeadm init --apiserver-advertise-address=192.168.208.3 --image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers --service-dns-domain=imsv2

kubectl -n kube-system get all
1
2
# 日志中关键错误
E1228 14:03:55.799748 1 main.go:234] Failed to create SubnetManager: error retrieving pod spec for 'kube-system/kube-flannel-ds-rbzdn': Get "https://10.96.0.1:443/api/v1/namespaces/kube-system/pods/kube-flannel-ds-rbzdn": dial tcp 10.96.0.1:443: connect: network is unreachable

网络性能调优

参考文章

CentOS7内核版本低导致部分域名解析失败

参考文章

简介

xv6-public GitHub

xv6-riscv Gitee

麻省理工6.828课程

英文文档

中文文档

xv6 是 MIT 开发的一个教学用的完整的类 Unix 操作系统,并且在 MIT 的操作系统课程 6.828 中使用。通过阅读并理解 xv6 的代码,可以清楚地了解操作系统中众多核心的概念,对操作系统感兴趣的同学十分推荐一读!这份文档是中文翻译的 MIT xv6 文档,是阅读代码过程中非常好的参考资料。

qemu调试xv6环境

环境搭建

下载xv6源码(riscv版本)

安装编译环境

1
sudo apt-get install -y qemu-system-misc binutils-riscv64-linux-gnu gcc-riscv64-linux-gnu gdb-multiarch

直接make编译,make qemu启动环境。

若使用VSCode进行调试,参考附录进行配置。

QEMU virt简介

QEMU riscv 启动代码

QEMU virt有8个hart,通过汇编指令csrr a1, mhartid可以读取hart对应的值。所有的hart都会执行执行内核代码。

Bootloader在启动后,会将内核放置在0x80000000

附录

VSCode一键调试

xv6-riscv目录中放置xv6源码

  • 已知问题,启动过程中QEMU直接退出,需要修改GDB配置文件.gdbinit
1
2
3
4
5
6
set confirm off
set architecture riscv:rv64
@REM target remote 127.0.0.1:26000
symbol-file kernel/kernel
set disassemble-next-line auto
set riscv use-compressed-breakpoints yes
  • tasks.json内容
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
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"options": {
"cwd": "${workspaceFolder}/xv6-riscv"
},
"tasks": [
{
"label": "xv6-qemu-task",
"type": "shell",
// "command": "echo all done",
"isBackground": true,
"command": "make && make qemu-gdb",
"problemMatcher": [
{
"pattern": [
{
"regexp": ".",
"file": 1,
"location": 2,
"message": 3
}
],
"background": {
"beginsPattern": ".*Now run 'gdb' in another window.",
"endsPattern": "."
}
}
],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
  • launch.json内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "xv6-qemu-gdb",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceRoot}/xv6-riscv/kernel/kernel",
"stopAtEntry": true,
"cwd": "${workspaceRoot}/xv6-riscv",
"miDebuggerServerAddress": "127.0.0.1:26000",
"miDebuggerPath": "/usr/bin/gdb-multiarch",
"MIMode": "gdb",
"preLaunchTask": "xv6-qemu-task",
}
]
}

简介

I2C驱动

开发板:CH32V307V-EVT-R1 RISC-V32

引脚复用:

1
2
3
4
5
6
7
8
9
10
11
参考:CH32V307DS0.PDF
PB5 <--> I2C1_SMBA
PB8 <--> I2C1_SCL
PB9 <--> I2C1_SDA
PB10 <--> I2C2_SCL
PB11 <--> I2C2_SDA
PB12 <--> I2C2_SMBA

I2C1 <--> I2C2
PB8 <--> PB10
PB9 <--> PB11

SPI驱动

USB驱动

为什么说指针是C语言的精髓? 指针有什么用 举例说明

C语言的指针和语言历史背景相关,这个得从在上个世纪60年代说起……

一位年轻小伙小丹(Dennis MacAlistair Ritchie),需要编写一个操作系统,但缺少合适的语言工具。那时B语言精炼且接近硬件,但过于简单且数据无类型。用汇编写引导程序合适,大型操作系统效率有点低。年轻人想法就是多,没有工具就自制工具上,于是小丹同学就顺手设计了C语言。

作为一门语言工具的目的很纯粹,为操作系统而生[旺柴]

  • 开发效率高的高级语言;
  • 能够直接操作硬件;
  • 编译后的代码执行效率高;

C作为高级语言,与同时期的高级语言比肯定不能弱,毕竟那时没有Java、C++、PHP、Go、Python、C#等高级语言,而且这一点和题目没什么关系[旺柴]


正式回到主题

1. 操作硬件

这个主要是应用在与硬件非常近的场景,主要用于读写特定寄存器,比如:嵌入式开发、驱动软件开发、操作系统开发等。

这里先以以简单的stm32f103为例。GPIOB8管脚有连接一个LED灯,此时若想点亮这个灯就需要控制管脚输出高电平:

1
2
3
4
5
6
7
8
9
#define GPIOB_BSRR *((volatile unsigned int *)(0x40010C00 + 0x10))

int main(void)
{
...
//点亮LED
GPIOB_BSRR = 1 << 8;
...
}

注意看,宏GPIOB_BSRR定义为*((volatile unsigned int *)0x40010C10),这里已经应用了指针的知识,这个地址就是GPIOB口的BSRR寄存器。至于为什么是这个地址和写入值的含义与处理器相关,具体可以查看STM32F103芯片手册。换一种写法就是

1
2
3
4
5
6
7
8
int main(void)
{
volatile unsigned int *pGPIO_BSRR = (volatile unsigned int *)0x40010C10;
...
//点亮LED
*pGPIO_BSRR = 0x100000000;
...
}

指针是一种变量,pGPIO_BSRR中存储的值是0x40010C10,则点亮LED语句就是向地址0x40010C10写入一个整数值0x100000000若不用指针用普通变量能表达向特定寄存器赋值的语义吗?显然是不能的

毕竟声明一个整数变量,它地址是0x40010C10概率还没我中大奖的概率大。每个特定处理器寄存器的地址是固定的,与内存地址范围没有交叠,所以编译器也不会给普通变量分配这样的地址。

部分同学可能不熟悉嵌入式,再找找上古Linux 0.11中有这么一个函数con_init,读取显示参数(0x90006地址存储显示参数在启动时获取的存储的)

1
2
3
4
5
6
7
8
#define ORIG_VIDEO_COLS (((*(unsigned short *)0x90006) & 0xff00) >> 8)

void con_init(void)
{
...
video_num_columns = ORIG_VIDEO_COLS;
...
}

换种写法就是

1
2
3
4
5
6
7
8
void con_init(void)
{
...
unsigned short *pORIG_VIDEO = (unsigned short *)0x90006;
unsigned short ORIG_VIDEO_COLS = ((*pORIG_VIDEO)>>8) & 0xff;
video_num_columns = ORIG_VIDEO_COLS;
...
}

这段就是将参数从内存地址0x90006中取出来,参数高字节就是显示列数。与嵌入式不同的是,这个地址就是普通内存地址,还是有运气遇到这个地址的[旺柴]

2. 执行效率高

其他老师不太清楚,我们像上课那会儿,老师怎么介绍指针的?

1
2
3
4
int a = 5;
int *pa = &a;
*pa = 6;
printf("a=%d\n", *pa);

本人:「老师,为啥不直接使用变量a

老师:「这里讲解指针pa的用途」

[旺柴]

其实,指针很多时候是为提高执行效率,避免大块数据拷贝。比如,在收到一个1.5K的网络二进制包,需要调用解析函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define PACKAGE_SIZE 1500

typedef struct net_raw_s
{
int data[PACKAGE_SIZE];
...
} net_raw_t;

int parse(const net_raw_t raw)
{
// 具体解析数据流程,保密
...
}

int main()
{
net_raw_t recv_net_data;
...
// 接收到网络数据准备解析
int ret = parse(recv_net_data);
...
}

每次调用parse函数都会拷贝net_raw_t类型数据,这个数据包有1.5K大小耗时非常多的。从执行效率考虑,此处非常适合使用指针方式,虽然要麻烦一点点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define PACKAGE_SIZE 1500

typedef struct net_raw_s
{
int data[PACKAGE_SIZE];
...
} net_raw_t;

int parse(net_raw_t *const raw)
{
// 具体解析数据流程,保密
...
}

int main()
{
net_raw_t recv_net_data;
...
// 接收到网络数据准备解析
int ret = parse(&recv_net_data);
...
}

本质上说,函数参数永远都是值传递,没有所谓的地址传递。函数参数raw指针变量只是赋值为recv_net_data变量的地址,没有所谓的地址传递。而指针的优势是,不管什么类型的指针,它自身都只占用4字节内存(32位),所以赋值效率高。若将函数parse修改为传递1.5K个指针,相信它的执行效率比最初版本parse还要慢。


还有一个抽象性应该很多高级语言都有,只是C语言需要利用指针来完成。

3. 抽象性

主要涉及函数指针,在模块设计时用处比较大,就是模块对外提供抽象接口。可以降低模块的耦合性,提升功能内聚性,提高协同开发效率,整体降低工程成本。这个特性在Linux内核中应用也非常广泛,下面主要以字符驱动为例进行说明。没接触过Linux字符驱动的建议先参考这篇文章

Linux设备驱动之字符设备驱动(超级详细~)

Linux字符设备驱动的通用操作

  • 初始化
  • 打开设备
  • 读数据
  • 写数据
  • 关闭设备
  • 等等操作

所有设备都按下面这个模板实现接口逻辑,就形成各种字符设备驱动。下面这个是并口打印机的驱动(源文件在linux-2.6.12/drivers/char/lp.c):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct file_operations {
...
int (*open) (struct inode *, struct file *);
...
};

static struct file_operations lp_fops = {
.owner = THIS_MODULE,
.write = lp_write,
.ioctl = lp_ioctl,
.open = lp_open,
.release = lp_release,
.read = lp_read,
};

static int lp_open(struct inode * inode, struct file * file)
{
...
}

在加载了这个驱动后,应用程序只需要调用openreadclose等接口就能操作这个字符设备。当然应用程序需要通过打印机打印信息时,需要先打开打印机设备,就要调用内核open函数。此时调用的open不是结构体struct file_operations中的函数指针,而是内核接口封装过的,但最终是调用lp_open,这样设备就初始化好就能继续操作。

在打开设备过程中,内核调用它们的都是结构体struct file_operations中的open函数指针。此时内核只关注要打开设备,而不关注设备如何打开,这些其实就是抽象。不管是打印机设备还是串口设备,只要需要打开动作,内核就负责找到设备的结构体struct file_operations中的open函数指针调用即可。

若没有这个抽象性,那编写内核时就要直接调用驱动接口。则一个开发人员既要掌握内核驱动管理逻辑细节,又要掌握设备驱动逻辑细节,最后软件维护成本必然很高。


上面说的这些都是了解的C语言指针的用处,只想说它太重要了,这也是这门上古语言能流传至今的秘宝。

以上都是个人对指针的理解,若有纰漏望轻喷[旺柴]

保护模式内存管理

vol-3 Chapter 3 Protected-Mode Memory Management

内存管理机制:段和页

开启保护模式会使能段机制,页机制是可选的。

<图片>Figure 3-1. Segmentation and Paging

完整内存管理机制开启后,地址转换:

Logical Address -段-> Linear Address -页-> Physical Address

详细转换流程

逻辑地址组成:段选择器 (线性地址)偏移

段选择器为全局描述表(GDT)的索引,就能找到段描述项,从中取出段基地址(线性地址空间),与偏移地址就能得到页描述项?

页描述组成:页目录索引 页表索引 (物理地址)偏移

页大小典型值为4K,当被访问的页(物理内存)不在当前内存中,则处理器会产生页错误(page-fault)异常

使用段机制

基本扁平模式(Basic Flat Model)

ROM分配在FFFF_FFF0H内存处,RAM在DS值为0时在内存底部

受保护扁平模式(Protected Flat Model)

访问不存在内存时,会触发通用保护(general-protection)异常

用户设置特权等级3的代码和数据段,管理员设置权限等级0的代码和数据段

多段模式(Multi-Segment Model)

逻辑与线性地址

图 Figure 3-5. Logical Address to Linear Address Translation

GDT或LDT

段选择器结构

图 Figure 3-6. Segment Selector

Index (位3-位15)可选择GDT或LDT中8K中的一项(每个段描述符大小为8字节)

TI (位2)表指示器,0使用GDT,1使用LDT

RPL (位0-1)特权选择器,0最高权限

6个段寄存器(CS DS SS ES FS GS),前三个必须加载有效的段选择器。每个寄存器都有可见的段选择器和不可见的基地址、限制和权限。

配置方式:

  • MOV, POP, LDS, LES, LSS, LGS, and LFS指令显示加载相应寄存器,MOV通常用作存储段寄存器可见区域
  • CALL, JMP, and RET, SYSENTER and SYSEXIT,IRET, INT n, INTO, INT3, and INT1指令隐形加载CS寄存器(有事有其他段寄存器)

段描述符

图 Figure 3-8. Segment Descriptor

Segment limit field 20位,指定段的大小。G==0,1Byte1MB,粒度1Byte;G==1,4KB4GB,粒度4KB;

Base address fields 24位,定义4GB线性地址空间的起始位置,推荐地址16字节对齐;

Type field 4位,指示段或门类型,并指定可以对段进行的访问类型和增长方向,功能由其他域决定;

S (descriptor type) flag 1位,指定段类型,0系统段,1代码或数据段;

DPL (descriptor privilege level) field 2位,指定段特权等级。0特权等级最高;

P (segment-present) flag 指示段是否在内存中,1存在,0不存在;

D/B (default operation size/default stack pointer size and/or upper bound) flag 1位,不同段描述符不同功能

G (granularity) flag 1位,指明段限制域缩放比例。0字节单位,1 4KB单位。

L (64-bit code segment) flag 1位

Available and reserved bits 1位,可供系统软件使用

保护

图 Figure 5-1. Descriptor Fields Used for Protection

中断和异常处理

表 Table 6-1. Protected-Mode Exceptions and Interrupts

基本结构

1
#!/bin/sh

变量

脚本所有变量都为字符串

声明变量

1
2
nodeList=
cmdBuild="build"

使用变量

1
2
cmd=$cmdBuild
cmdFile=${cmdBuild}_file.log

截断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ list="app _install util _install scripts logs"
$ echo $list
app _install util _install scripts logs

# 将标识符_install及尾部去除,只保留头部
$ echo ${list%_install*}
app _install util
$ echo ${list%%_install*scri*}
app

# 将标识符_install及头部去除,只保留尾部
$ echo ${list#*_install}
util _install scripts logs
$ echo ${list##*_install}
scripts logs

数组

声明数组

1
2
3
4
5
6
7
8
9
10
11
defaultNodeList=`ls -d */ | awk '{gsub(/\//,""); print $0}'`

# 直接输出
$ echo $defaultNodeList
app _install util

# 循环输出
$ for node in $defaultNodeList; do echo $node; done
node: app
node: _install
node: util

增加元素

1
2
3
4
# 数组尾部增加元素
defaultNodeList="$defaultNodeList tailItem"
# 数组头部增加元素
defaultNodeList="headItem $defaultNodeList"

删除元素

方法1,字符串截断,列表不能有重复元素

1
2
delim="_install"
defaultNodeList="${defaultNodeList%%${delim}*} ${defaultNodeList##*${delim}}"

方法2,关键字替换,可以替换所有元素

方法3,重新创建新数组,将指定元素丢弃

判断

单个条件

1
2
3
4
5
6
7
if [ -z "$1" ]; then
echo "Update cmd is empty, skip it."
elif [ -z "$2" ]; then
echo "Directory($s) is runtime, skip it."
else
echo "All is ok."
fi

多个条件

1
2
3
if [ ! -z "$nodeList" ] && [ "$cmd" = "$cmdBuild" ]; then
echo "Update cmd is empty, skip it."
fi

switch型

1
2
3
4
5
6
7
8
case "$1" in
-n) updateNodeList $2; shift; shift; ;;
--start) shift; updateCmd $cmdStart; ;;
--stop) shift; updateCmd $cmdStop; ;;
-b|--build) shift; updateCmd $cmdBuild; ;;
-h|--help) usage; ;;
*) error "Unknown option($1)"; usage; ;;
esac

循环

while型

1
2
3
4
while [ -n "$1" ]; do
echo "$1"
shift
done

for each型

1
2
3
for node in $nodeList; do
echo "node: $node"
done

函数

声明函数

1
2
3
4
info()
{
echo "\033[32m[INFO]" $*"\033[0m"
}

输入参数:$1~$9 分别代表参数1~9. $0为执行脚本文件路径

返回参数:return [RET],值为0-255;不写值为默认0;

调用函数

直接调用

1
$ info "Exec cmd $cmd." "All is ok"

获取返回值判断,类似三元表达式

1
2
3
4
5
6
# 单步处理可以直接写
testPid $pid && echo "$pid is ok" || echo "$pid is failed"

# 多步处理需要增加括号
testPid $pid && info "$node($pid) start done." ||
(error "$node($pid) start failed." && echo `tail -n 5 $pwdPath/$node/${node}_out.log` && exit 255)

常用命令

返回文件的绝对路径

1
2
$ readlink -f go.mod 
/root/project/softarch/go.mod

dirname

返回文件或文件夹的上级路径

1
2
$ dirname ~/project/softarch
/root/project

echo

显示字符串

1
2
echo "hello world"
echo "hello world\n你好,世界\n"

控制显示颜色

1
2
3
4
5
6
# 绿色字体
echo "\033[32m[INFO] hello world\033[0m"
# 黄色字体
echo "\033[33m[WARN] hello world\033[0m"
# 红色字体
echo "\033[31m[ERROR] hello world\033[0m"

nohup

脱离终端启动程序

启动程序时,获取程序运行pid

1
nohup ./$node >$pwdPath/$node/${node}_out.log 2>&1 & echo $!>/tmp/.${node}.pid

组合命令

显示当前路径下所有文件夹名称

1
2
3
4
$ ls -d */ | awk '{gsub(/\//,""); print $0}'
app
_install
util

Rust

oldlinux

《Linux内核0.11完全注释》0.11

Bochs

qemu+gdb

调试 Linux 最早期的代码

网上修改版本:https://github.com/yuan-xy/Linux-0.11

ubuntu16.04.7

1
2
3
4
5
6
7
8
# 带UI启动qemu
qemu-system-x86_64 -m 16M -boot a -fda Image -hda $(HDA_IMG) -s -S

# 其他终端启动gdb
gdb tool/system
target remote :1234

# VSCode ssh远程连接后启动调试

launch.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"version": "0.2.0",
"configurations": [
{
"name": "gdb-0.11",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/Linux-0.11-master/tools/system",
"miDebuggerServerAddress": "127.0.0.1:1234",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/Linux-0.11-master",
"environment": [],
"externalConsole": false,
"MIMode": "gdb"
}
]
}

vscode打印内存地址数据,调试控制台

1
2
3
4
# 执行gdb命令x,打印0x0处内存,打印32字节
# xb,字节(默认)
# xw,4字节
-exec x/32xb 0x0

正式源码下载

《Linux内核源代码情景分析》 2.4.0

《Linux Kernel Development》2.6

qemu+gdb

Linux性能调试工具

简介

perf是Linux 2.6+内核中的一个工具,在内核源码包中的位置 tools/perf。

perf利用Linux的trace特性,可以用于实时跟踪,统计event计数(perf stat);或者使用采样(perf record),报告(perf report|script|annotate)的使用方式进行诊断。

perf命令行接口并不能利用所有的Linux trace特性,有些trace需要通过ftrace接口得到。

参考perf-tools

安装

Ubuntu20.04直接安装

1
apt-get install linux-tools-$(uname -r) linux-tools-generic

使用

运行程序后获取pid

1
perf record -a -g -v -p <PID> sleep 30

等待30秒后会得到perf.data原始记录文件,直接查看

1
perf report -g --tui

生成火焰图perf-main.svg

1
2
3
4
git clone https://github.com/brendangregg/FlameGraph
mv perf.data FlameGraph/
cd FlameGraph
perf script | ./stackcollapse-perf.pl | ./flamegraph.pl > perf-main.svg

perf-main.svg需要使用浏览器打开,即可方便查看

生成热力图类似

1
2
3
4
5
git clone https://github.com/brendangregg/HeatMap      # or download it from github
mv perf.data HeatMap/
cd HeatMap
perf script | awk '{ gsub(/:/, "") } $5 ~ /issue/ { ts[$6, $10] = $4 } $5 ~ /complete/ { if (l = ts[$6, $9]) { printf "%.f %.f\n", $4 * 1000000, ($4 - l) * 1000000; ts[$6, $10] = 0 } }' > out.lat_us
./trace2heatmap.pl --unitstime=us --unitslat=us --maxlat=50000 out.lat_us > out.svg

入门基础

环境搭建

VSCode + 浏览器

必备插件

HTML CSS Support

官方推荐的插件,自动补全

open in browser

快速在浏览器中查看网页

error lens

报错显示

CSS Peek

追踪样式

Auto Rename Tag

成对修改标签。VS自带的重名名好像已经具备。

工程

常用资料

MDN

MDN官方网站

核心知识

JavaScriptHTML中的一个script标签,很多地方需要遵守HTML的规范。

结构

js代码存在多种方式:内部、外部、内联。

内部方式

1
2
3
4
5
6
<body>
<!-- 内部js -->
<script>
// 具体逻辑
</script>
</body>

HTML标签是按照顺序加载进入,一般推荐写在</body>标签之前,可以尽量保障所有标签都能访问到。

外部方式

1
2
3
4
<body>
<!-- 外部js -->
<script src="js文件所在位置"></script>
</body>

值得注意的是,body标签中引入script,会忽略script标签中的代码。

内联方式

这种方式在前端框架vue中使用比较多。

1
2
3
4
<body>
<!-- 内联js -->
<button onclick="alert('逗你玩~~~')">点击我月薪过万</button>
</body>

行结尾符

C++类似使用英文;,但很多场合结尾符是可以省略的,推荐都省略掉。

常用输入输出

window下面的函数调用可以省略window

函数功能说明
window.alert弹窗输出信息
document.write文档输出信息,支持解析HTML
console.log控制台输出信息
window.prompt弹窗输入信息

注释

C++一行,单行注释使用//,多行注释使用/**/

1
2
3
4
5
// 单行注释

/*
多行注释
*/

变量

命名规则

  • 不能使用保留关键字
  • 使用下划线、字母、数字、$,不能以数字开头
  • 区分字母大小写

数字Number

js中数字不区分整型和小数

1
2
let n1 = 10
let n2 = 3.14

运算异常时,会返回NaN

布尔Boolean

1
2
let b1 = true
let b2 = false

字符串String

字面量支持反引号,可以将数字和字符串输出,类似格式化输出。

1
2
3
4
let s1 = 'hello'
let s2 = "world"
let s3 = `Pi is ${n2}`
let s4 = '3.14'

可以使用\转义符。

+可以连接两个字符串字面量或变量。

字符串转换数字

当使用+s4可以将字符串转换为数字。

使用函数parseIntparseFloat

数组Array

js中数组不要求每个元素要一致,类似其他语言的元组。

简单声明如下:

1
2
3
4
const arr1 = [1, 2, 3]
const arr2 = ['hello', 'world']
const arr3 = [1, 'hello']
const arr4 = new Array(1, 'hello')

常用数组方法

新增成员

push在数组尾部插入成员

unshift在数组头部插入成员

删除成员

pop删除数组尾部成员

shift删除数组头部成员

splice删除或替换数组指定区域的成员

修改成员

通过下标索引

访问成员

通过下标索引

对象

js中对象支持动态扩展成员及方法,所有对象都是Object。对象默认使用引用方式进行赋值,所以一般使用const进行声明即可。

简单声明

1
2
3
4
const obj1 = {
uname: 'Tik',
cnt: 0,
}

增加成员

对象.成员 = 值

删除成员

delete 对象.成员

修改成员

对象.成员 = 新值

访问成员

若成员名称符合语法

对象.成员

若名称不符合语法,比如:需要访问'number 1 - cnt'成员

对象[成员]

undefined

默认为赋值的变量都会返回undefined。运算结果一般都为undefined

null

可以运算,结果与另一个算子相关。

内置Math对象

常用方法

方法说明
random随机值
ceil向上取整
floor向下取整
max最大值
min最小值
pow幂运算
abs绝对值

声明关键词

关键词const声明常量,声明后不能改变常量本身js中常量本身存放的是对象或数组的地址,因此常量支持修改成员,例如:

1
2
const arr1 = []
arr1.push(1)

关键词let声明局部变量,同作用域不支持同名变量。

关键词var声明全局变量,无论在代码何处声明,其声明都在所有代码之前。但赋值操作还是保留原位置,推荐使用let替代声明变量。

typeof可以返回变量的类型。

运算符

算术运算符

与其他语言基本运算符一致。

特别的是对==!=做了扩充,两等只判断值是否相等。===!==不仅仅判断值是否相等,同时要判断类型是否相等。比如:

1
2
3
4
5
6
7
const str = '3.14'
const num = 3.14
console.log(`str == num : ${str == num}`)
console.log(`str === num : ${str === num}`)

// str == num : true
// str === num : false

运算优先级

优先级运算符说明
1( )
2++ -- !
3* / % + -+-优先级较低
4> >= < <=
5== != === !==
6&& ||||优先级较低
7=
8,

逻辑中断

原理是表达式逻辑判断时,遇到局部值时确定表达式返回值时会直接返回,此时局部值为表达式值。

或逻辑

A||B,A为真则为A,否则为B。适用于变量赋值A是表达式,B是默认值。

与逻辑

A&&B,A为假则为A,否则为B。

控制语句

与其他语言类似:if for while

函数

普通函数

支持将声明放在作用域尾部。

声明方式

function 函数名(参数列表){函数逻辑}

调用方式

匿名函数

使用方式

作为函数参数

function (参数列表) {}

作为函数变量

let fun1 = function (参数列表) {}

调用方式

(function (参数列表) {})()

(function (参数列表) {}())

箭头函数Arrow function

与普通函数不同点是,箭头函数没有自己的thisarguments动态参数,只能使用剩余参数。

基本格式:()=>{}

省略规则

  • 只有一个形参可以省略()x=>{}
  • 只有一行代码可以省略大括号,此时return需要省略
  • 只有一行返回对象时需要加()=> ({uname:uname})

可变参数

arguments,函数属性,动态参数,是伪数组

...xxx,剩余参数,是真数组

DOM

文档对象模型(DOM,Document Object Model)是一组API,用于操作网页元素。在js中以树状结构组成,通过操作各对象(树节点)的属性和HTML结构。

document

document.documentElement HTML的根节点

基本操作

对象属性

元素内容顶部到它的视口可见内容的顶部的距离

scrollTop 顶部的距离

scrollLeft 左边的距离

元素布局尺寸,包含padding border

offsetWidth 宽度

offsetHeight 高度

元素布局位置,受父级元素定位影响

offsetLeft 元素左边相对父级元素的位置

offsetTop 元素顶部相对父级元素的位置

children 返回仅元素节点的伪数组

nextElementSibling 返回下一兄弟节点

previousElementSibling 返回上以兄弟节点

对象方法

getBoundingClientRect 返回元素的大小及其相对于视口的位置

parentNode 返回父级节点

childNodes 返回所有子节点,包括文本、注释

appendChild 在子节点列表增加节点

insertBefore 在子节点列表参考节点前插入节点

cloneNode 克隆对象,默认false不克隆子节点

removeChild 删除子节点

获取对象

获取第一个元素querySelector('css选择器')

返回所有匹配的元素querySelectorAll('css选择器')

css选择器简单回忆

  • 选择指定类class='active'.active
  • 选择特定标签divdiv
  • 选择特定子级标签<div><p class='active'><i></i></p></div>: div i
  • 选择特定亲子级标签<div><p class='active'></p></div>: div>.active
  • 选择特定序号子级标签<div><p class='active'></p><p></p><p></p></div>: div p:nth-child(2)

修改对象

修改元素内容

innerText 不会解析子标签

innerHTML 会解析子标签

修改元素的对象属性

href 跳转地址

title 标题

src 图片源

修改元素样式


直接修改style属性。需要注意,使用下划线的要改为小驼峰方式,没有自动补全。

基本格式

对象.style.样式属性 = 值

优点

  • 适合小范围样式调整
  • 行内样式,权重很高

通过className操作css

基本格式

对象.className = '类名或列表(空格分隔)'

优点

  • 直接替换旧值

:thumbsup:通过classList控制css

注意,类名不能加英文.

基本格式

  • 追加,对象.classList.add('类名')
  • 删除,对象.classList.remove('类名')
  • 切换,对象.classList.toggle('类名')

优点

  • 小范围批量更改样式,旧样式不受影响

操作表单属性

基本格式

表单.value = 值

表单.type = 类型值

部分表单属性使用布尔值表示,例如:disabled checked selected

标签支持自定义属性,自定义属性在HTML中以data-自定义属性名格式最为名称。js需要获取自定义属性时,使用对象.dataset.自定义属性名

Date对象

新建

new Date()

new Date('2022-5-1 8:30')

方法

getFullYear() 获取年份

getMonth() 获取月份,0代表1月

getDate() 返回月份中的天

getDay() 获取星期,0代表星期天

getHours() 获取小时数

getMinutes() 获取分钟数

getSeconds() 获取秒数

获取毫秒时间戳

new Date().getTime()

+new Date()

Date.now()

Document对象

对象属性

document.documentElement.scrollTop = Y

对象方法

write 向文档中写入数据

createElement 创建特定元素节点

Console对象

console.log

事件

基本概念

简介

在系统内发生的动作或事情。要产生事件并完成处理,需要

  • 事件源,即dom对象
  • 事件类型
  • 事件处理函数

事件监听经历多个版本。

DOM L0

基本格式为:对象.on事件类型 = 处理函数

不支持对同一事件多次绑定。

:thumbsup:DOM L2

基本格式:对象.addEventLisener('事件类型', 处理函数)

支持对同一事件多次绑定,推荐使用。

事件流

指事件完整执行的流动路径。

addEventListener第三个参数为是否在捕获阶段触发,默认为false为冒泡阶段。

事件流主要两个阶段:

  • 捕获阶段,Document->Element html->Element body->Element div
  • 冒泡阶段,Element div->Element body->Element html->Document

阻止事件流(事件冒泡)

事件对象.stopPropagation()

事件委托

减少注册次数,提高程序性能。

利用冒泡的特点来完成。

通过父级元素来处理多个子级元素的事件。

基本操作

事件绑定

基本格式:对象.addEventLisener('事件类型', 处理函数)

其中,事件类型见下面的章节。

处理函数可以带参数,第一个参数为Event对象,一般参数命名为evente

Event对象重要属性

  • target 实际出发事件的对象
  • type 事件类型
  • clientX/Y 光标相对浏览器的位置
  • offsetX/Y 光标相对DOM对象的位置
  • key 用户按下键盘值(‘Enter’ ‘a’ ‘A’)

普通处理函数中,this会指向target

事件解绑

L0 对象.on事件类型 = null

L2 对象.removeEventListener(事件类型,回调函数,关键参数)

需要注意的是,L2绑定的方式不支持匿名解绑。

事件类型

鼠标事件

  • click 鼠标点击:thumbsup:
  • mouseenter 鼠标进入,没有冒泡效果:thumbsup:
  • mouseleave 鼠标离开,没有冒泡效果:thumbsup:
  • mouseover 鼠标划过,有冒泡效果
  • mouseout 鼠标离开,有冒泡效果

焦点事件

  • focus 获得焦点
  • blur 失去焦点

键盘事件

  • Keydown 按键按下
  • Keyup 按键抬起

文本事件

  • input 用户输入
  • change 内容发生变化

多媒体事件

  • timeupdate 音视频当前播放位置变化
  • loadeddata 音视频当前播放帧加载完毕

页面事件

  • load 等待特定资源加载完毕
  • DOMContentLoaded 等待HTML节点加载完成
  • resize 当页面尺寸发生变化时

触摸屏事件

  • touchstart 触摸开始
  • touchmove 触摸后并发生移动
  • touchend 触摸结束

BOM

基本结构

window对象作为根节点,包含如下节点对象:

  • navigator
  • location
  • document
  • history
  • screen

Window对象

对象属性

window.scrollTo(X,Y)

定时器

时间到时触发回调函数,并自动开启下次定时

setInterval 开启定时器

clearInterval 关闭定时器

输入

弹出输入框

prompt(消息,默认值)

输出

弹出警告框

alert(消息)

定时器-延时函数

时间到后出发回调函数,不会自动开启下次定时

  • setTimeout 设置定时器
  • clearTimeout 删除定时器

本地存储

数据存储在用户浏览器中。设置、读取方便、甚至页面刷新不丢失数据。容量较大,sessionStoragelocalStorage大约可以存储5M大小的数据。

所有方法都支持KV方式存储。

localStorage

数据生命周期没有过期时间,直到用户主动清理。

实例方法

setItem 存储值

getItem 读取值

sessionStorage

用法同localStorage,生命周期与浏览器窗口一致。同一页面数据共享。

复杂数据类型处理

核心是将对象转换为json字符串

JSON.stringify 将对象序列化为json字符串

JSON.parsejson字符串反序列化为对象

Location对象

对象属性

href 直接跳转到指定的网页 search 返回地址中?开始的参数 hash 返回#开始的哈希值

对象方法

reload 刷新页面

对象属性

userAgent 检测浏览器信息,可以用于检测客户端平台

History对象

对象方法

back 网页后退 forward 网页前进 go 网页前进或后退

JS执行机制

最大特点为单线程。所有任务需要排队执行。任务可以分为两种:

  • 同步任务。在主线程上执行,形成执行栈。
  • 异步任务。js的异步任务是通过回调实现的。

执行流程:

  • 先执行执行栈的同步任务。
  • 异步任务放入任务队列中。
  • 执行栈中的所有同步任务执行完,会依次读取任务队列的异步任务,异步任务结束等待状态,进入执行栈,开始执行。

这种机制被称为事件循环(event loop)

特殊技巧

第三方推荐

swiper

触摸滑动插件swiper官网

正则表达式

语法

对象声明:const 变量名 = /表达式/

对象方法

test 返回是否匹配

exec 返回匹配的数组

字符串.replace 是字符串的方法,替换匹配文本

元字符

边界符:^ $

量词:* + ? {n} {n,} {m,n}

字符类:[abc] [a-z] [^abc] .

预定义:\d [0-9] \D [^0-9] \w [A-Za-z0-9_] \W [^A-Za-z0-9_] \s [\t\r\n\v\f] \S [^\t\r\n\v\f]

修饰符

使用格式:/表达式/修饰符

i 不区分大小写

g 全局匹配

参考资料

MDN 正则表达式

在线测试工具

进阶用法

作用域

局部 外部无法访问 var声明的是全局变量

作用域链 优先在当前作用域查找 逐级查找父级作用域直到全局

GC

垃圾回收机制(GC,Garbage Collection)

计数方式 复杂对象都有计数,计数为0则会被回收 无法解决嵌套引用(循环引用) 标记清除法 “无法到达的对象”

闭包

​ 一个函数对周围状态的引用捆绑在一起 ​ 简单理解 ​ 闭包=内层函数+外层函数的变量 ​ 可能会引起内存泄漏

展开符号

​ let arr = [1,2,3] console.log(…arr) ​ 求最值 Math.max(…arr) Math.min(…arr) ​ 合并数组 arr = […arr1, …arr2]

解构赋值

​ 数组解构 ​ 目标是简洁语法和快速为变量赋值 ​ 将数组的单元值快速批量赋值给变量的简洁语法 const [max,min,avg]=[5,1,3] ​ let [a, b] = [1, 2]; [b, a] = [a, b] ​ 剩余参数 const [a,…b] = [1,2,3] ​ 可以设置默认值 const [a=0,b=0]=[] ​ 忽略部分值 const [a,,c] = [1,2,3] ​ 多维数组 const [a,b,[c,d]]=[1,2,[3,4]] ​ 对象解构 ​ const { uname, age } = { uname: ‘pink’, age: 18 } ​ 被赋值的变量与属性名称需要一致 ​ 重命名 const { uname: username, age: userage } = { uname: ‘pink9’, age: 19} ​ 多级对象 const {name,family:{mother,father}}={name:‘佩奇’, family:{mother:‘猪妈妈’,father:‘猪爸爸’}}

拷贝

​ 浅拷贝 ​ 深拷贝 ​ 递归实现 ​ lodash/cloneDeep实现 ​ JSON字符串转换

异常处理

​ 抛异常 throw new Error(‘参数不能为空’) ​ 异常捕获 try{}catch(err){} ​ 异常时也会执行 finally{} ​ 自动启动调试停止处 debugger

改变this

​ 直接调用 fn.call(new_this, arg_list…) ​ 直接调用,参数是数组 fn.apply(new_this [,arg_list…]) ​ 先绑定返回新的函数 fn.bind(new_this)

防抖

​ 原理 ​ 规定时间内,每次触发都会取消上次执行,只执行最后一次动作 ​ 场景 ​搜索框智能搜索 ​ 输入框输入检测 ​ loadash提供防抖处理 ​ 使用setTimeout实现

节流-throttle

​ 原理 ​ 规定时间内,每次触发都会检测上次执行是否完成,只有完成后才会执行新的动作 ​场景 ​ 高频事件 ​ 鼠标移动 ​ 页面尺寸缩放 ​ 滚动条变化 ​ 多媒体播放进度更新 ​ loadash提供节流处理 ​ 使用setTimeout实现

面向对象

对象

​ 创建 ​ 字面量 const obj = {name:’‘} ​ const obj = new Object({name:’‘}) ​ 构造函数 function Good(name){ this.name = name } const obj = new Good(’’) ​ 属性 ​ Good.count = 5 静态属性 ​ 方法 ​ Good.print = function(){…} 静态方法

内置对象

​ Object ​ Object.keys 返回对象所有属性键 ​ Object.values 返回对象所有值 ​Object.assign 对象拷贝(与克隆不一样,没有的值不会覆盖) ​ Array ​ forEach 遍历数组 ​ filter 过滤数组 ​ map 迭代数组 ​ reduce 累加器 ​ 其他常用 ​ join ​ find ​ every ​ some ​ concat ​ sort ​ splice ​ reverse ​ findIndex ​ Array.from 伪数组转为真数组 ​ Number ​ toFixed 设置保留小数位的长度

面向对象

​ 简介 ​ 每个构造函数有prototype原型对象 ​ prototype默认会有constructor属性,指向构造函数 ​ 所有对象实例都有一个属性__proto__指向prototype ​ 浏览器中显示为[[Prototype]] ​ 构造函数和原型对象中的this都指向实例化对象 ​ 手动赋值prototype ​ Star.prototype={ constructor: Star, sing:… dance:… } ​ 继承 ​ 原型继承 Man.prototype = new Person() Man.prototype.constructor = Man ​ 原型链 ​ 原型对象会指向父级? ​ 顶级对象是Object ​ 当访问属性或方法时,优先查找本地,没有再到父级中查找,直到为null ​ instanceof

0%