共计 14434 个字符,预计需要花费 37 分钟才能阅读完成。
写在后面
钻研 K8S 有一段时间了,最开始学习 K8S 时,依据网上的教程装置 K8S 环境总是报错。所以,我就扭转了学习策略,先不搞环境搭建了。先通过官网学习了 K8S 的整体架构,底层原理,又硬啃了一遍 K8S 源码。别问我为哈这样学,只是我感觉对我集体来说,这样学能让我更好的了解整套云原生体系。这不,这次,我总结了如何一次性胜利装置 K8S 集群的办法。咱们明天先来说说如何基于一主两从模式搭建 K8S 集群。前面,咱们再上如何齐全无坑搭建 K8S 高可用集群的计划。
文章和搭建环境所须要的 yml 文件已收录到:https://github.com/sunshinelyz/technology-binghe 和 https://gitee.com/binghe001/technology-binghe。如果文件对你有点帮忙,别忘记给个 Star 哦!
集群布局
IP | 主机名 | 节点 | 操作系统版本 |
---|---|---|---|
192.168.175.101 | binghe101 | Master | CentOS 8.0.1905 |
192.168.175.102 | binghe102 | Worker | CentOS 8.0.1905 |
192.168.175.103 | binghe103 | Worker | CentOS 8.0.1905 |
根底配置
在三台服务器上的 /etc/hosts 文件中增加如下配置项。
192.168.175.101 binghe101
192.168.175.102 binghe102
192.168.175.103 binghe103
查看零碎环境
别离在三台服务器上查看零碎的环境。
1. 查看服务器操作系统版本
cat /etc/redhat-release
装置 Docker 和 K8S 集群的服务器操作系统版本须要在 CentOS 7 以上。
2. 查看服务器的主机名
hostname
留神:集群中服务器的主机名不能是 localhost。
3. 查看服务器的 CPU 核数
lscpu
留神:集群中服务器的 CPU 核数不能少于 2 个。
4. 查看服务器网络
以 binghe101(Master)服务器为例。在服务器上执行 ip route show
命令来查看服务器的默认网卡,如下所示。
[root@binghe101 ~]# ip route show
default via 192.168.175.2 dev ens33 proto static metric 100
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown
192.168.175.0/24 dev ens33 proto kernel scope link src 192.168.175.101 metric 100
在下面的输入信息中有如下一行标注了 binghe101 服务器所应用的默认网卡。
default via 192.168.175.2 dev ens33 proto static metric 100
能够看到,binghe101 服务器应用的默认网卡为 ens33。
接下来,应用 ip address
命令查看服务器的 IP 地址,如下所示。
[root@binghe101 ~]# ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:0c:29:68:06:63 brd ff:ff:ff:ff:ff:ff
inet 192.168.175.101/24 brd 192.168.175.255 scope global noprefixroute ens33
valid_lft forever preferred_lft forever
inet6 fe80::890f:5a92:4171:2a11/64 scope link noprefixroute
valid_lft forever preferred_lft forever
能够看到,binghe101 服务器上的默认网卡的 IP 地址为192.168.175.101
,K8S 将应用此 IP 地址与集群内的其余节点通信。集群中所有 K8S 所应用的 IP 地址必须能够互通。
Docker 装置
别离在三台服务器上安装 Docker 并配置阿里云镜像加速器。
1. 装置 Docker
新建 auto_install_docker.sh 脚本文件
vim auto_install_docker.sh
文件的内容如下所示。
export REGISTRY_MIRROR=https://registry.cn-hangzhou.aliyuncs.com
dnf install yum*
yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
dnf install https://mirrors.aliyun.com/docker-ce/linux/centos/7/x86_64/stable/Packages/containerd.io-1.2.13-3.1.el7.x86_64.rpm
yum install docker-ce docker-ce-cli -y
systemctl enable docker.service
systemctl start docker.service
docker version
或者指定 Docker 的版本进行装置,此时 auto_install_docker.sh 脚本文件的内容如下所示。
export REGISTRY_MIRROR=https://registry.cn-hangzhou.aliyuncs.com
dnf install yum*
yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum install -y docker-ce-19.03.8 docker-ce-cli-19.03.8 containerd.io
systemctl enable docker.service
systemctl start docker.service
docker version
应用如下命令赋予 auto_install_docker.sh 文件可执行权限。
chmod a+x ./auto_install_docker.sh
接下来,间接运行 auto_install_docker.sh 脚本文件装置 Docker 即可。
./auto_install_docker.sh
2. 配置阿里云镜像加速器
新建脚本文件 aliyun_docker_images.sh。
vim aliyun_docker_images.sh
文件内容如下所示。
mkdir -p /etc/docker
tee /etc/docker/daemon.json <<-'EOF'
{"registry-mirrors": ["https://zz3sblpi.mirror.aliyuncs.com"]
}
EOF
systemctl daemon-reload
systemctl restart docker
为 aliyun_docker_images.sh 脚本文件赋予可执行权限,如下所示。
chmod a+x ./aliyun_docker_images.sh
执行 aliyun_docker_images.sh 脚本文件配置阿里云镜像加速器。
./aliyun_docker_images.sh
零碎设置
别离在三台服务器上进行零碎设置。
1. 装置 nfs-utils
yum install -y nfs-utils
yum install -y wget
2. 敞开防火墙
systemctl stop firewalld
systemctl disable firewalld
3. 敞开 SeLinux
setenforce 0
sed -i "s/SELINUX=enforcing/SELINUX=disabled/g" /etc/selinux/config
4. 敞开 swap
swapoff -a
yes | cp /etc/fstab /etc/fstab_bak
cat /etc/fstab_bak |grep -v swap > /etc/fstab
5. 批改 /etc/sysctl.conf
新建 sys_config.sh 脚本文件。
vim sys_config.sh
sys_config.sh 脚本文件的内容如下所示,
# 如果有配置,则批改
sed -i "s#^net.ipv4.ip_forward.*#net.ipv4.ip_forward=1#g" /etc/sysctl.conf
sed -i "s#^net.bridge.bridge-nf-call-ip6tables.*#net.bridge.bridge-nf-call-ip6tables=1#g" /etc/sysctl.conf
sed -i "s#^net.bridge.bridge-nf-call-iptables.*#net.bridge.bridge-nf-call-iptables=1#g" /etc/sysctl.conf
sed -i "s#^net.ipv6.conf.all.disable_ipv6.*#net.ipv6.conf.all.disable_ipv6=1#g" /etc/sysctl.conf
sed -i "s#^net.ipv6.conf.default.disable_ipv6.*#net.ipv6.conf.default.disable_ipv6=1#g" /etc/sysctl.conf
sed -i "s#^net.ipv6.conf.lo.disable_ipv6.*#net.ipv6.conf.lo.disable_ipv6=1#g" /etc/sysctl.conf
sed -i "s#^net.ipv6.conf.all.forwarding.*#net.ipv6.conf.all.forwarding=1#g" /etc/sysctl.conf
# 可能没有,追加
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
echo "net.bridge.bridge-nf-call-ip6tables = 1" >> /etc/sysctl.conf
echo "net.bridge.bridge-nf-call-iptables = 1" >> /etc/sysctl.conf
echo "net.ipv6.conf.all.disable_ipv6 = 1" >> /etc/sysctl.conf
echo "net.ipv6.conf.default.disable_ipv6 = 1" >> /etc/sysctl.conf
echo "net.ipv6.conf.lo.disable_ipv6 = 1" >> /etc/sysctl.conf
echo "net.ipv6.conf.all.forwarding = 1" >> /etc/sysctl.conf
# 执行命令以利用
sysctl -p
执行如下命令赋予 sys_config.sh 文件可执行权限。
chmod a+x ./sys_config.sh
执行 sys_config.sh 脚本文件。
./sys_config.sh
装置 K8S
别离在三台服务器上安装 K8S。
1. 配置 K8S yum 源
新建 k8s_yum.sh 脚本文件。
vim k8s_yum.sh
文件的内容如下所示。
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
赋予 k8s_yum.sh 脚本文件的可执行权限。
chmod a+x ./k8s_yum.sh
执行 k8s_yum.sh 文件。
./k8s_yum.sh
2. 卸载旧版本的 K8S
yum remove -y kubelet kubeadm kubectl
3. 装置 kubelet、kubeadm、kubectl
yum install -y kubelet-1.18.2 kubeadm-1.18.2 kubectl-1.18.2
4. 批改 docker Cgroup Driver 为 systemd
sed -i "s#^ExecStart=/usr/bin/dockerd.*#ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --exec-opt native.cgroupdriver=systemd#g" /usr/lib/systemd/system/docker.service
5. 重启 docker,并启动 kubelet
systemctl daemon-reload
systemctl restart docker
systemctl enable kubelet && systemctl start kubelet
综合装置脚本
综上,上述装置 Docker、进行零碎设置,装置 K8S 的操作能够对立成 auto_install_docker_k8s.sh 脚本。脚本的内容如下所示。
# 装置 Docker 19.03.8
export REGISTRY_MIRROR=https://registry.cn-hangzhou.aliyuncs.com
dnf install yum*
yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum install -y docker-ce-19.03.8 docker-ce-cli-19.03.8 containerd.io
systemctl enable docker.service
systemctl start docker.service
docker version
#配置阿里云镜像加速器
mkdir -p /etc/docker
tee /etc/docker/daemon.json <<-'EOF'
{"registry-mirrors": ["https://zz3sblpi.mirror.aliyuncs.com"]
}
EOF
systemctl daemon-reload
systemctl restart docker
#装置 nfs-utils
yum install -y nfs-utils
yum install -y wget
#敞开防火墙
systemctl stop firewalld
systemctl disable firewalld
#敞开 SeLinux
setenforce 0
sed -i "s/SELINUX=enforcing/SELINUX=disabled/g" /etc/selinux/config
# 敞开 swap
swapoff -a
yes | cp /etc/fstab /etc/fstab_bak
cat /etc/fstab_bak |grep -v swap > /etc/fstab
#批改 /etc/sysctl.conf
# 如果有配置,则批改
sed -i "s#^net.ipv4.ip_forward.*#net.ipv4.ip_forward=1#g" /etc/sysctl.conf
sed -i "s#^net.bridge.bridge-nf-call-ip6tables.*#net.bridge.bridge-nf-call-ip6tables=1#g" /etc/sysctl.conf
sed -i "s#^net.bridge.bridge-nf-call-iptables.*#net.bridge.bridge-nf-call-iptables=1#g" /etc/sysctl.conf
sed -i "s#^net.ipv6.conf.all.disable_ipv6.*#net.ipv6.conf.all.disable_ipv6=1#g" /etc/sysctl.conf
sed -i "s#^net.ipv6.conf.default.disable_ipv6.*#net.ipv6.conf.default.disable_ipv6=1#g" /etc/sysctl.conf
sed -i "s#^net.ipv6.conf.lo.disable_ipv6.*#net.ipv6.conf.lo.disable_ipv6=1#g" /etc/sysctl.conf
sed -i "s#^net.ipv6.conf.all.forwarding.*#net.ipv6.conf.all.forwarding=1#g" /etc/sysctl.conf
# 可能没有,追加
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
echo "net.bridge.bridge-nf-call-ip6tables = 1" >> /etc/sysctl.conf
echo "net.bridge.bridge-nf-call-iptables = 1" >> /etc/sysctl.conf
echo "net.ipv6.conf.all.disable_ipv6 = 1" >> /etc/sysctl.conf
echo "net.ipv6.conf.default.disable_ipv6 = 1" >> /etc/sysctl.conf
echo "net.ipv6.conf.lo.disable_ipv6 = 1" >> /etc/sysctl.conf
echo "net.ipv6.conf.all.forwarding = 1" >> /etc/sysctl.conf
# 执行命令以利用
sysctl -p
# 配置 K8S 的 yum 源
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
# 卸载旧版本 K8S
yum remove -y kubelet kubeadm kubectl
# 装置 kubelet、kubeadm、kubectl,这里我装置的是 1.18.2 版本,你也能够装置 1.17.2 版本
yum install -y kubelet-1.18.2 kubeadm-1.18.2 kubectl-1.18.2
# 批改 docker Cgroup Driver 为 systemd
# # 将 /usr/lib/systemd/system/docker.service 文件中的这一行 ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
# # 批改为 ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --exec-opt native.cgroupdriver=systemd
# 如果不批改,在增加 worker 节点时可能会碰到如下谬误
# [WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd".
# Please follow the guide at https://kubernetes.io/docs/setup/cri/
sed -i "s#^ExecStart=/usr/bin/dockerd.*#ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --exec-opt native.cgroupdriver=systemd#g" /usr/lib/systemd/system/docker.service
# 设置 docker 镜像,进步 docker 镜像下载速度和稳定性
# 如果您拜访 https://hub.docker.io 速度十分稳固,亦能够跳过这个步骤
# curl -sSL https://kuboard.cn/install-script/set_mirror.sh | sh -s ${REGISTRY_MIRROR}
# 重启 docker,并启动 kubelet
systemctl daemon-reload
systemctl restart docker
systemctl enable kubelet && systemctl start kubelet
docker version
留神:我装置的 K8S 版本是 1.18.2,大家在装置 K8S 时,也能够抉择其余版本进行装置
赋予 auto_install_docker_k8s.sh 脚本文件可执行权限。
chmod a+x ./auto_install_docker_k8s.sh
执行 auto_install_docker_k8s.sh 脚本文件。
./auto_install_docker_k8s.sh
留神:须要在每台服务器上执行 auto_install_docker_k8s.sh 脚本文件。
初始化 Master 节点
只在 binghe101 服务器上执行的操作。
1. 初始化 Master 节点的网络环境
# 只在 master 节点执行
# export 命令只在以后 shell 会话中无效,开启新的 shell 窗口后,如果要持续装置过程,请从新执行此处的 export 命令
export MASTER_IP=192.168.175.101
# 替换 k8s.master 为 您想要的 dnsName
export APISERVER_NAME=k8s.master
# Kubernetes 容器组所在的网段,该网段装置实现后,由 kubernetes 创立,当时并不存在于您的物理网络中
export POD_SUBNET=172.18.0.1/16
echo "${MASTER_IP} ${APISERVER_NAME}" >> /etc/hosts
2. 初始化 Master 节点
在 binghe101 服务器上创立 init_master.sh 脚本文件,文件内容如下所示。
#!/bin/bash
# 脚本出错时终止执行
set -e
if [${#POD_SUBNET} -eq 0 ] || [${#APISERVER_NAME} -eq 0 ]; then
echo -e "\033[31;1m 请确保您曾经设置了环境变量 POD_SUBNET 和 APISERVER_NAME \033[0m"
echo 以后 POD_SUBNET=$POD_SUBNET
echo 以后 APISERVER_NAME=$APISERVER_NAME
exit 1
fi
# 查看残缺配置选项 https://godoc.org/k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2
rm -f ./kubeadm-config.yaml
cat <<EOF > ./kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
kubernetesVersion: v1.18.2
imageRepository: registry.cn-hangzhou.aliyuncs.com/google_containers
controlPlaneEndpoint: "${APISERVER_NAME}:6443"
networking:
serviceSubnet: "10.96.0.0/16"
podSubnet: "${POD_SUBNET}"
dnsDomain: "cluster.local"
EOF
# kubeadm init
# 依据您服务器网速的状况,您须要等待 3 - 10 分钟
kubeadm init --config=kubeadm-config.yaml --upload-certs
# 配置 kubectl
rm -rf /root/.kube/
mkdir /root/.kube/
cp -i /etc/kubernetes/admin.conf /root/.kube/config
# 装置 calico 网络插件
# 参考文档 https://docs.projectcalico.org/v3.13/getting-started/kubernetes/self-managed-onprem/onpremises
echo "装置 calico-3.13.1"
rm -f calico-3.13.1.yaml
wget https://kuboard.cn/install-script/calico/calico-3.13.1.yaml
kubectl apply -f calico-3.13.1.yaml
赋予 init_master.sh 脚本文件可执行权限。
chmod a+x ./init_master.sh
执行 init_master.sh 脚本文件。
./init_master.sh
3. 查看 Master 节点的初始化后果
(1)确保所有容器组处于 Running 状态
# 执行如下命令,期待 3-10 分钟,直到所有的容器组处于 Running 状态
watch kubectl get pod -n kube-system -o wide
如下所示。
[root@binghe101 ~]# watch kubectl get pod -n kube-system -o wide
Every 2.0s: kubectl get pod -n kube-system -o wide binghe101: Sat May 2 23:40:33 2020
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
calico-kube-controllers-5b8b769fcd-l2tmm 1/1 Running 0 3m59s 172.18.203.67 binghe101 <none> <none>
calico-node-8krsl 1/1 Running 0 3m59s 192.168.175.101 binghe101 <none> <none>
coredns-546565776c-rd2zr 1/1 Running 0 3m59s 172.18.203.66 binghe101 <none> <none>
coredns-546565776c-x8r7l 1/1 Running 0 3m59s 172.18.203.65 binghe101 <none> <none>
etcd-binghe101 1/1 Running 0 4m14s 192.168.175.101 binghe101 <none> <none>
kube-apiserver-binghe101 1/1 Running 0 4m14s 192.168.175.101 binghe101 <none> <none>
kube-controller-manager-binghe101 1/1 Running 0 4m14s 192.168.175.101 binghe101 <none> <none>
kube-proxy-qnffb 1/1 Running 0 3m59s 192.168.175.101 binghe101 <none> <none>
kube-scheduler-binghe101 1/1 Running 0 4m14s 192.168.175.101 binghe101 <none> <none>
(2)查看 Master 节点初始化后果
kubectl get nodes -o wide
如下所示。
[root@binghe101 ~]# kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
binghe101 Ready master 5m43s v1.18.2 192.168.175.101 <none> CentOS Linux 8 (Core) 4.18.0-80.el8.x86_64 docker://19.3.8
初始化 Worker 节点
1. 获取 join 命令参数
在 Master 节点上执行如下命令获取 join 命令参数。
kubeadm token create --print-join-command
具体执行如下所示。
[root@binghe101 ~]# kubeadm token create --print-join-command
W0502 23:44:55.218947 59318 configset.go:202] WARNING: kubeadm cannot validate component configs for API groups [kubelet.config.k8s.io kubeproxy.config.k8s.io]
kubeadm join k8s.master:6443 --token s0hoh1.2cwyf1fyyjl2h04a --discovery-token-ca-cert-hash sha256:6d78e360dc64d84762611ac6beec8ac0f0fe9f72a5c2cca008df949e07827c19
其中,有如下一行输入。
kubeadm join k8s.master:6443 --token s0hoh1.2cwyf1fyyjl2h04a --discovery-token-ca-cert-hash sha256:6d78e360dc64d84762611ac6beec8ac0f0fe9f72a5c2cca008df949e07827c19
这行代码就是获取到的 join 命令。
留神:join 命令中的 token 的无效工夫为 2 个小时,2 小时内,能够应用此 token 初始化任意数量的 worker 节点。
2. 初始化 Worker 节点
针对所有的 worker 节点执行,在这里,就是在 binghe102 服务器和 binghe103 服务器上执行。
创立 init_worker.sh 脚本文件,文件内容如下所示。
# 只在 worker 节点执行
# 192.168.175.101 为 master 节点的内网 IP
export MASTER_IP=192.168.175.101
# 替换 k8s.master 为初始化 master 节点时所应用的 APISERVER_NAME
export APISERVER_NAME=k8s.master
echo "${MASTER_IP} ${APISERVER_NAME}" >> /etc/hosts
# 替换为 master 节点上 kubeadm token create 命令输入的 join
kubeadm join k8s.master:6443 --token s0hoh1.2cwyf1fyyjl2h04a --discovery-token-ca-cert-hash sha256:6d78e360dc64d84762611ac6beec8ac0f0fe9f72a5c2cca008df949e07827c19
其中,kubeadm join… 就是 master 节点上 kubeadm token create 命令输入的 join。
赋予 init_worker.sh 脚本文件文件可执行权限,并执行 init_worker.sh 脚本文件。
chmod a+x ./init_worker.sh
./init_worker.sh
3. 查看初始化后果
在 Master 节点执行如下命令查看初始化后果。
kubectl get nodes -o wide
如下所示。
[root@binghe101 ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
binghe101 Ready master 20m v1.18.2
binghe102 Ready <none> 2m46s v1.18.2
binghe103 Ready <none> 2m46s v1.18.2
留神:kubectl get nodes 命令前面加上 -o wide 参数能够输入更多的信息。
重启 K8S 集群引起的问题
1.Worker 节点故障不能启动
Master 节点的 IP 地址发生变化,导致 worker 节点不能启动。须要重新安装 K8S 集群,并确保所有节点都有固定的内网 IP 地址。
2.Pod 解体或不能失常拜访
重启服务器后应用如下命令查看 Pod 的运行状态。
kubectl get pods --all-namespaces
发现很多 Pod 不在 Running 状态,此时,须要应用如下命令删除运行不失常的 Pod。
kubectl delete pod <pod-name> -n <pod-namespece>
留神:如果 Pod 是应用 Deployment、StatefulSet 等控制器创立的,K8S 将创立新的 Pod 作为代替,重新启动的 Pod 通常可能失常工作。
重磅福利
微信搜一搜【冰河技术】微信公众号,关注这个有深度的程序员,每天浏览超硬核技术干货,公众号内回复【PDF】有我筹备的一线大厂面试材料和我原创的超硬核 PDF 技术文档,以及我为大家精心筹备的多套简历模板(不断更新中),心愿大家都能找到心仪的工作,学习是一条时而郁郁寡欢,时而开怀大笑的路,加油。如果你通过致力胜利进入到了心仪的公司,肯定不要懈怠放松,职场成长和新技术学习一样,逆水行舟。如果有幸咱们江湖再见!
另外,我开源的各个 PDF,后续我都会继续更新和保护,感激大家长期以来对冰河的反对!!