背景
参考B站k8s教程构建一个k8s集群. 由于B站视频是基于windows的vmware构建的集群, 本文讲解如何在Mac 上面用等价的VMware Fusion完成这个过程. 以及对其中某些不必要组件进行的删减.
准备
首先是为什么选择VMware Fusion来构建k8s的集群做实验. 实时上docker desktop这样的工具也可以让我们熟悉如何操作k8s. 但是使用docker desktop不能模拟多个节点,自然就无法真正去模拟多节点k8s集群的效果. 其次 docker desktop k8s集群在网络这一部分的表现也和标准的k8s集群不同, 对比B站教程, 在docker desktop上的操作行为可能不同, 不便于理解对k8s的一些资源的理解. 综上所述, 采用VMware Fusion虚拟机的方案替代docker desktop来完成k8s集群试验. Windows上的虚拟机方案采用VMware来完成, Mac上使用的是VMware Fusion.两个虚拟机软件使用差异并不算太大, 在构建集群的时候唯一需要注意的即是VMware Fusion的网络适配器配置, 稍微和windows 上的VWware有些许区别.
- 资源清单
一台Mac Book Pro 安装上 VMware Fusion 11.5.6
三台虚拟机构成k8s节点(一主两从)安装CentOS7操作系统
一台虚拟机做harbor私有镜像仓库 安装CentOS7操作系统
操作系统的安装不在赘述,和VMware上的安装几乎无差别.我并没有创建教程中的koolshare
软路由这台虚拟机. 因为这一部分在原视频教程中挺鸡肋的, k8s依赖的docker 镜像都可以离线安装,完全没必要构建一个软路由翻出去访问google的服务器下载依赖的镜像, 所以这一部分我在构建集群的时候忽略掉了.
网络配置
整个过程所需要的网络适配器, 只需要配置一个NAT网络适配器即可.点击VMware fusion -> preferences -> network
增加一个自定义的虚拟网络, 我这里叫vmnet2.
然后勾选 Allow virtual machines...(using NAT)
以及 Connect the host Mac to this network
保证一个初始化的NAT虚拟网络构建成功. VMware fusion的网络配置稍微比windows 上的VMware 要复杂一些. 初始化之后打开终端, 进入到/Library/Preferences/VMware\ Fusion
目录中, 里面有Fusion相关的网络配置.
这里需要更改两个文件, 一个是当前目录下的networking
里面保存了此机器上VMware Fusion的所有虚拟网路的配置信息. 只需要更改之前增加的vmnet2这个网络配置, 此处需要配置子网网段 以及 子网掩码. 和教程保持一致, 子网网段选择 192.168.66.0/24
然后进入到vmnet2
子文件夹中 更改 nat.conf
这个文件 配置网关和子网掩码
自此新建的自定义网络已经全部配置完成. 下面需要让配置生效, 一个简单的方法, 依然是打开VMware Fusion -> preferences -> network
去掉 Connect the host Mac to this network
的勾选, 点击 apply 再勾选 Connect the host Mac to this network
然后再apply. 至此,集群所需的NAT网络适配器已经完成构建.
下面是为每一个节点配置静态的网络.
现在已经确定网段是192.168.66.0/24
和 网关192.168.66.2
. 可以为每台机器分配如下的静态IP地址.
k8s主节点: k8s-master01 192.168.66.10
k8s从节点: k8s-node01 192.168.66.20
k8s从节点: k8s-node02 192.168.66.21
harbor镜像仓库节点: harbor 192.168.66.100
选择Centos7系统镜像安装虚拟机(安装过程不再赘述), 然后配置虚拟机的静态IP. 更改配置文件/etc/sysconfig/network-scripts/ifcfg-ens33
添加或者更改如下内容:
BOOTPROTO=static
ONBOOT=yes
IPADDR=192.168.66.10
NETMASK=255.255.255.0
GATEWAY=192.168.66.2
DNS1=192.168.66.2
上面是k8s-master01节点的网络配置, 其他节点更改相应的IPADDR
即可.
配置完成后重启网络systemctl restart network
测试一下能ping通即可. 集群基本网络配置就此结束.
系统初始化
当完成四台机器的基本Centos7系统安装,静态IP配置以及验证网络通信正常的工作之后,接下来是为安装k8s做一些初始化工作.
- 设置主机名以及修改hosts文件
hostnamectl set-hostname k8s-master01
为每一个节点设置主机名称, 我采用的主机名为k8s-master01, k8s-node01, k8s-node02, harbor.
修改hosts文件, 保证机器之间可以通过主机名相互访问
192.168.66.10 k8s-master01
192.168.66.20 k8s-node01
192.168.66.21 k8s-node02
192.168.66.100 harbor
上述操作在一台机器上即可(k8s-master01), 然后使用
scp /etc/hosts root@k8s-node01:/etc/hosts
scp /etc/hosts root@k8s-node02:/etc/hosts
...
将hosts文件拷贝到所有的节点上.
- 安装系统依赖
yum install -y conntrack ntpdate ntp ipvsadm ipset jq iptables curl sysstat libseccomp wget vim net-tools git
- 防火墙设置
systemctl stop firewalld && systemctl disable firewalld && yum -y install iptables-services && systemctl start iptables && systemctl enable iptables && iptables -F && service iptables save
- 关闭SELINUX
swapoff -a && sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
setenforce 0 && sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config
- 调整内核参数
cat > kubernetes.conf <<EOF
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-ip6tables=1
net.ipv4.ip_forward=1
net.ipv4.tcp_tw_recycle=0
vm.swappingness=0 #禁止使用 swap 空间
vm.overcommit_memory=1 # 不检查物理内存是否够用
vm.panic_on_oom=0 # 启用oom
fs.inotify.max_user_instances=8192
fs.inotify.max_user_watches=1048576
fs.file-max=52706963
fs.nr_open=52706963
net.ipv6.conf.all.disable_ipv6=1
net.netfilter.nf_conntrack_max=2310720
EOF
执行拷贝
cp kubernetes.conf /etc/sysctl.d/kubernetes.conf
sysctl -p /etc/sysctl.d/kubernetes.conf
此处操作可能会遇到一些error, 先不用管,后续会升级系统内核.
- 调整系统时区
timedatectl set-timezone Asia/Shanghai
timedatectl set-local-rtc 0
systemctl restart rsyslog
systemctl restart crond
- 关闭不需要的服务
systemctl stop postfix && systemctl disable postfix
- 设置 rsyslogd 和 systemd journald
mkdir /var/log/journal
mkdir /etc/systemd/journald.conf.d
cat > /etc/systemd/journald.conf.d/99-prophet.conf <<EOF
[Journal]
# 持久化保存到磁盘
Storage=persistent
# 压缩历史日志
Compress=yes
SyncIntervalSec=5m
RateLimitInterval=30s
RateLimitBurst=1000
# 最大占用空间 10G
SystemMaxUse=10G
# 单日志文件最大200M
SystemMaxFileSize=200M
# 日志保存时间为 2 周
MaxRetentionSec=2week
# 不将日志发送到 syslog
ForwardToSyslog=no
EOF
systemctl restart systemd-journald
- 升级系统内核
rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm
yum --enablerepo=elrepo-kernel install -y kernel-lt
设置以新的内核默认启动系统并且重启. 注意此处的内核版本要根据自己的实际升级的内核版本定, 比如我自己的环境里面版本名称就和视频教程的不一样.
建议重启的时候选择4.x版本的内核进入, 在uname -r
查看详细内核版本, 再是有如下的命令设置默认启动的内核版本并重启
grub2-set-default 'CentOS Linux (4.4.236-1.el7.elrepo.x86_64) 7 (Core)' && reboot
重启后使用umane -r
查看默认启动的内核版本
[root@k8s-master01 project]# uname -r
4.4.236-1.el7.elrepo.x86_64
升级到4.x版本且默认启动, 那么内核的升级就是成功的. 至此安装部署k8s的准备工作完成了.
kubeadm部署k8s集群
部署k8s集群之前,需要完成docker的安装, 不仅仅三个k8s集群节点虚拟机需要安装docker, harbor节点也需要安装docker, 它充当一个docker镜像仓库的角色,是需要安装docker的.
- kube-proxy 开启ipvs的前置条件
modprobe br_netfilter
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 && bash /etc/sysconfig/modules/ipvs.modules && lsmod | grep -e ip_vs -e nf_conntrack_ipv4
- 安装docker
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 update -y && yum install -y docker-ce
注意:安装完成,重启之后内核默认启动会回到 3.x 版本, 所以需要重新设置默认启动内核 在重启. 这里依然需要查看自己的机器的4.x内核版本, 不同环境和教程的版本可能是不一致的
grub2-set-default 'CentOS Linux (4.4.236-1.el7.elrepo.x86_64) 7 (Core)' && reboot
再次重启后 uname -r
查看默认启动版本是4.x版本, 且不存在问题后,再执行后续操作.
systemctl start docker && systemctl enable docker
- 配置docker daemon
cat > /etc/docker/daemon.json <<EOF
{
"exec-opts" : ["native.cgroupdriver=systemd"],
"log-driver" : "json-file",
"log-opts" : {
"max-size" : "100m"
}
}
EOF
mkdir -p /etc/systemd/system/docker.service.d
重启docker服务
systemctl daemon-reload && systemctl restart docker && systemctl enable docker
注意到此处, k8s集群三个节点和 harbor节点都已经安装上了docker. 后续部署k8s只需要在k8s三个指定虚拟机节点上进行, 而安装harbor则是在harbor机器节点上进行, 先进行kubeadm 部署k8s集群的工作, 后进行harbor私有仓库的构建.
- 安装kubeadm
cat > /etc/yum.repos.d/kubernetes.repo <<EOF
[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
yum -y install kubeadm-1.15.1 kubectl-1.15.1 kubelet-1.15.1
systemctl enable kubelet.service
初始化主节点之前, 需要准备k8s必须的docker 镜像, 一般情况下这些镜像是需要从google的服务器下载的, 我们在构建之初就没有考虑去构建软路由, 通过软路由去访问google服务器下载镜像. 实际上教程中已经帮我们打包好了k8s必须的docker 镜像压缩包(k8s所需离线资源包)中解压后的kubeadm-basic.images.tar.gz
这个压缩包. 只需要把这个压缩包导入到三个k8s集群节点, 并且使用docker load XXX
这些必须的docker 镜像即可.
首先先把压缩的docker镜像资源包从宿主机器分别拷贝到三个虚拟机中.
scp ./kubeadm-basic.images.tar.gz root@192.168.66.10:~
scp ./kubeadm-basic.images.tar.gz root@192.168.66.20:~
scp ./kubeadm-basic.images.tar.gz root@192.168.66.21:~
分别对三个机器中的这个镜像资源进行解压
tar -zxvf kubeadm-basic.images.tar.gz
解压后 会在 /root/kubeadm-basic.images
解压目录下存放所有的docker镜像.
此时可以一个一个镜像通过docker load XXX
导入, 但是效率太低.
准备如下的一个shell 脚本load-images.sh
#!/bin/bash
ls /root/kubeadm-basic.images > /tmp/image-list.txt
cd /root/kubeadm-basic.images
for i in $( cat /tmp/image-list.txt)
do
docker load -i $i
done
rm -rf /tmp/image-list.txt
chmod a+x ./load-images.sh
分别在三台机器上执行此脚本, 完成k8s必须镜像的导入.
然后使用docker images
做基本验证即可.
- 初始化主节点
kubeadm 构建k8s集群也是通过yaml
配置文件来实现的, 首先我们需要获取一个最基本最简单的配置文件, 执行如下命令
kubeadm config print init-defaults > kubeadm-config.yaml
得到配置文件, 修改此配置文件
localAPIEndPoint:
advertiseAddress: 192.168.66.10
kubernetesVersion: v1.15.1
networking:
podSubnet: "10.244.0.0/16"
serviceSubnet: 10.96.0.0/12
# 下面为添加的关于ipvs配置的字段
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
featureGates:
SupportIPVSProxyMode: true
mode: ipvs
以此更改的yaml文件作为集群主节点初始化配置文件, 执行:
kubeadm init --config=kubeadm-config.yaml --experimental-upload-certs | tee kubeadm-init.log
初始化成功后当前目录会有一个kubeadm-init.log
的初始化日志文件, 里面包含了很多集群相关的信息.
还需执行如下的三个相关指令
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
查看节点状态
kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-master01 NotReady master 9m5s v1.15.1
尚未用flannel构建扁平的网络, 所以状态是NotReady.
在部署网路之前, 先整理一下目前的文件. 一直操作的目录是root目录, 此目录下与k8s相关的文件是kubeadm-config.yaml
和 kubeadm-init.log
.
当前目录下创建一个install-k8s
目录用于保存k8s集群相关的所有配置文件
在install-k8s
目录下创建一个core
目录和 plugin
目录. core
用于存放kubeadm-config.yaml
和 kubeadm-init.log
. plugin
目录用于存放一些插件,此处是flannel
. 在plugin
目录中 创建一个flannel
目录. 进入此目录
执行
wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
下载flannel相关的资源清单文件之后执行
kubectl apply -f ./kube-flannel.yml
查看kube-system
名称空间下的pod
kubectl get pod -n kube-system
coredns-5c98db65d4-2sv7v 1/1 Running 0 19m
coredns-5c98db65d4-c9gnr 1/1 Running 0 19m
etcd-k8s-master01 1/1 Running 0 19m
kube-apiserver-k8s-master01 1/1 Running 0 19m
kube-controller-manager-k8s-master01 1/1 Running 0 19m
kube-flannel-ds-z8tn5 1/1 Running 0 59s
kube-proxy-7dqks 1/1 Running 0 19m
kube-scheduler-k8s-master01 1/1 Running 0 18m
发现和flannel相关的pod已经启动并开始工作了, 再次查看节点状态
kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-master01 Ready master 21m v1.15.1
主节点状态已经完全正常了.
然后将 install-k8s
文件加放到/usr/local
目录下, 避免被无意删除
mv install-k8s /usr/local
在查看/usr/local/install-k8s
整个目录结构如下:
install-k8s/
|-- core
| |-- kubeadm-config.yaml
| `-- kubeadm-init.log
`-- plugin
`-- flannel
`-- kube-flannel.yml
至此主节点的部署配置完毕, 接下来是其他两个节点的加入.
查看kubeadm-init.log
日志文件中的最后一行,有告知其他从节点如何加入到集群,在另外两个节点上执行如下操作即可.
kubeadm join 192.168.66.10:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:4b119a011d2e0591f539df74ea939ef02bcc2b7ac8be846a0a78b9a0744d8e70
再次主节点上查看整个集群状态
kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-master01 Ready master 25m v1.15.1
k8s-node01 Ready <none> 43s v1.15.1
k8s-node02 Ready <none> 37s v1.15.1
发现所有节点状态正常. 至此一个最简单的三节点k8s集群构建完毕. 当然此集群目前不是高可用的,后续会写如何构建高可用的k8s集群.在harbor私有镜像仓库构建完毕之后,再做整个集群功能的验证.
harbor私有镜像仓库构建(optional)
私有仓库的构建也是可选的. 上述在四台虚拟机上装好docker之后, 就在k8s集群的三台虚拟机上执行的部署,现在回到harbor这台机器上面. 首先是更改docker的 /etc/docker/daemon.json
文件, 添加一行内容
{
"exec-opts" : ["native.cgroupdriver=systemd"],
"log-driver" : "json-file",
"log-opts" : {
"max-size" : "100m"
},
"insecure-registries": ["https://hub.lab.com"]
}
指定了一个url这个 url是自己定义的私有仓库的url, 我这边用的是https://hub.lab.com
这个url.
重启docker
systemctl restart docker
其他三个k8s节点中的docker也需要加这些配置并且重启.
并且在所有机器的/etc/hosts
文件中添加
192.168.66.100 hub.lab.com
保证其他机器都能以域名访问harbor节点.
- 安装harbor
准备资源文件(k8s离线资源包)解压后的 harbor-offline-installer-v1.2.0.tgz
和 docker-compose
和之前离线安装一样, 先将宿主机器上的docker-compose
拷贝到 harbor节点
上的 /usr/local/bin/
下, harbor-offline-installer-v1.2.0.tgz
拷贝到虚拟机的root
目录下.
chmod a+x /usr/local/bin/docker-compose
然后在root
目录下解压
tar -zxvf ./harbor-offline-installer-v1.2.0.tgz
解压到了harbor目录下, 将此目录移动到/usr/local
mv harbor /usr/local
需要更改/usr/local/harbor
目录下的harbor.cfg
配置harbor节点的hostname和协议.
hostname = hub.lab.com
ui_url_protocol = https
harbor.cfg
默认的证书路径在/data/cert
目录下,因此需要创建此目录,并在此目录下完成证书的相关操作
mkdir -p /data/cert
在此目录下(/data/cert
)完成如下操作
生成私钥
openssl genrsa -des3 -out server.key 2048
其中会涉及到操作私钥的密码, 自己试验用最简单的123456
即可.
创建证书请求
openssl req -new -key server.key -out server.csr
会涉及到填写上一步输入的密码, 以及hostname
设置为自己定义的域名,我这边是用的hub.lab.com
私钥备份
cp server.key server.key.org
将私钥文件的密码退去
openssl rsa -in server.key.org -out server.key
最后进行签名
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
赋予文件操作权限
chmod a+x *
在进入 /usr/local/harbor
目录下执行./install.sh
完成harbor的安装.
功能验证
功能验证主要包含两部分,一个是harbor私有镜像仓库功能验证, 一个是k8s集群的功能验证.
harbor 私有镜像仓库功能验证
私有镜像仓库的验证主要是验证k8s集群能否从harbor仓库拉取镜像,以及能否push镜像到harbor仓库.
首先是宿主机器上访问harbor仓库.
可以在宿主机器的/etc/hosts
中添加harbor仓库的ip地址
92.168.66.100 hub.lab.com
在浏览器中输入hub.lab.com
进入到harbor的login页面
username: admin
password: Harbor12345
宿主机器可以访问harbor仓库. 下面是验证k8s集群中的节点访问harbor仓库.
因为本身harbor仓库等价于我们常用的docker hub 官方镜像仓库. 因此只要其他k8s节点上面使用
docker login https://hub.lab.com
输入用户名密码 能够显示登陆成功即可证明 k8s集群中节点能够访问harbor仓库.
先在master01节点从官方docker hub下载镜像
docker pull wangyanglinux/myapp:v1
harbor 对镜像命名是有规范的, 如{hostname}/{project}/{image}:tag
我用的默认的library
这个project, 因此我对刚才从docker hub拉取的镜像进行重命名如下:
docker tag wangyanglinux/myapp:v1 hub.lab.com/library/myapp:v1
然后再把它push到 harbor
docker push hub.lab.com/library/myapp:v1
然后通过宿主机器浏览器查看harbor中镜像已经有了
至此harbor的基本功能验证结束.
k8s集群功能验证
k8s集群基本功能验证, 主要是验证pod能否正常启动, service能否正常创建,集群内服务是否能够在集群中消费, 能否在外部消费集群中的服务.
以刚才harbor中的一个基本nginx镜像(hub.lab.com/library/myapp:v1
)构建 deployment.
kubectl run nginx-deployment --image=hub.lab.com/library/myapp:v1 --port=80 --replicas=1
查看deployment状态,一切正常
kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 1/1 1 1 55s
查看pod运行状态
kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-55c6669554-thgxf 1/1 Running 0 2m10s 10.244.2.2 k8s-node02 <none> <none>
集群内部容器访问的ip是10.244.2.2
且运行与k8s-node02
节点
ssh登录到 k8s-node02
节点, 检查相关容器正常运行
docker ps -a | grep nginx
8d4a35749762 hub.lab.com/library/myapp "nginx -g 'daemon of…" 3 minutes ago Up 3 minutes k8s_nginx-deployment_nginx-deployment-55c6669554-thgxf_default_cdd359a1-daad-435b-816f-259176540874_0
237642eae980 k8s.gcr.io/pause:3.1 "/pause" 3 minutes ago Up 3 minutes k8s_POD_nginx-deployment-55c6669554-thgxf_default_cdd359a1-daad-435b-816f-259176540874_0
测试pod内的程序是否可访问
curl 10.244.2.2
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
任意一个节点访问10.244.2.2
都能够得到正确结果,证明集群的扁平化网络运行正常.
对目标deployment 扩容
kubectl scale --replicas=3 deployment/nginx-deployment
创建service做负载均衡
kubectl expose deployment nginx-deployment --port=30000 --target-port=80
查看servie状态
kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 25h
nginx-deployment ClusterIP 10.99.93.51 <none> 30000/TCP 4s
此service是默认的ClusterIP
也是只可在集群内访问, "暴露"端口30000
访问此service
curl 10.99.93.51:30000
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
能够正常访问, service正常工作
查看ip对应关系
ipvsadm -Ln
TCP 10.99.93.51:30000 rr
-> 10.244.1.2:80 Masq 1 0 1
-> 10.244.1.3:80 Masq 1 0 1
-> 10.244.2.2:80 Masq 1 0 1
再看三个pod的ip地址
kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-55c6669554-jl5jj 1/1 Running 0 11m 10.244.1.2 k8s-node01 <none> <none>
nginx-deployment-55c6669554-nz9dl 1/1 Running 0 11m 10.244.1.3 k8s-node01 <none> <none>
nginx-deployment-55c6669554-thgxf 1/1 Running 0 21m 10.244.2.2 k8s-node02 <none> <none>
可以看到service完成了负载均衡的工作.
多次call
curl 10.99.93.51:30000/hostname.html
也能看到不同的hostname信息, service也能正常工作.
外部访问测试
service默认类型是 ClusterIP
不能够对集群外服务, 需要将此类型设置为NodePort
kubectl edit svc nginx-deployment
查看更改后的service
kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 25h
nginx-deployment NodePort 10.99.93.51 <none> 30000:32681/TCP 11m
发现多了一个32681
的随机映射端口, 此时从外部访问k8s集群任意一个节点的32681
端口都能够成功
至此整个k8s集群的基本功能验证完毕.
来源:oschina
链接:https://my.oschina.net/u/4274145/blog/4616656