赶在 2019 年快结束之际,写一篇博客作为本年度的收官之作吧。

前言

虽然现在各大云平台厂商都提供了一键搭建 kubenetes 的服务,但缺点是费用太贵了,如果仅仅把它作为自己没事折腾的小玩具非常不划算,另外虽然也可以用公司的账号,但并不想把自己的折腾的东西跟工作混为一谈。

所以决定自己在主机上手动搭建一个。

准备工作

一般 kubenetes 至少要三台 linux 主机组建成一个 cluster,因为手头没有三台 linux 物理主机,所以要用虚拟机代替。

kubenetes 各个虚拟机节点的规划如下:

主机名主机 IPOS集群角色
192-168-56-10.master192.168.56.10Ubuntu 18.04master
192-168-56-11.node192.168.56.11Ubuntu 18.04node1
192-168-56-12.node192.168.56.12Ubuntu 18.04node2

准备工作主要为下面几步:

  1. 物理主机内存 16G,操作系统为 Ubuntu 18.04
  2. 安装 Virtualbox 6.1
  3. 创建三台虚拟机,每台内存 4G
  4. 对三台虚拟机做基本配置

VirtualBox 虚拟机配置

默认只有一个 NAT 适配器,我们需要添加一个 Host-Only Adapter。NAT 适配器是虚拟机用来访问互联网的,Host-Only 适配器是用来虚拟机之间通信的。上面表格所指的 主机 IP 也是这个 Host only IP。

vbox 上配置一个 host only adapter, 默认名字 vboxnet0,网络地址 192.168.56.1/24

VirtualBox > File > Host Network Manager > Add (you will get vboxnet0)

virtualbox

接着每台虚拟机再加一个网卡,所以每台现在有两个网卡,一个 NAT 到互联网,一个用于本地 cluster 相互通信。

VM's Settings > System > check "Enable I/O APIC." 
VM's Settings > Network > Adapter 2 > host-only vboxnet0

配置好网卡以后,host only 的网卡 enp0s8 需要设置静态地址,master 的示例如下:

test@master:~$ cat /etc/netplan/50-cloud-init.yaml
network:
    ethernets:
        enp0s3:
            dhcp4: true
        enp0s8:
            addresses:
                - 192.168.56.10/24
            dhcp4: false
    version: 2

修改好以后,

$ sudo netplan generate
$ sudo netplan apply

可以看到,虚拟机有两个 ip 地址,10 开头的用于外部通信,192 开头的用于相互通信,

之后 k8s 搭建过程中都是用的 192 地址,不会用到 10 开头的,这个地址是虚拟机从网络上下载各种软件时自动选择使用。

enp0s3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.0.2.4  netmask 255.255.255.0  broadcast 10.0.2.255
        inet6 fe80::a00:27ff:fee6:c5c7  prefixlen 64  scopeid 0x20<link>
        ether 08:00:27:e6:c5:c7  txqueuelen 1000  (Ethernet)
        RX packets 514969  bytes 748728432 (748.7 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 294223  bytes 18448150 (18.4 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

enp0s8: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.56.10  netmask 255.255.255.0  broadcast 192.168.56.255
        inet6 fe80::a00:27ff:fe76:4048  prefixlen 64  scopeid 0x20<link>
        ether 08:00:27:76:40:48  txqueuelen 1000  (Ethernet)
        RX packets 213918  bytes 20708155 (20.7 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 220695  bytes 95399920 (95.3 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

虚拟机上安装 docker

这里的需要注意的是不要用 ubuntu 自带的 apt install, 也不要用 ubuntu 新推出的 snap install,两者都不对。

推荐的是从 docker 官方源安装,其实也很简单,把下面的所有命令运行一遍就 OK。

sudo apt update
sudo apt install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"
sudo apt update

sudo apt install docker-ce
sudo systemctl status docker

关闭 swap 分区

没什么好说的,官方文档明确说了。

运行 swapoff -a 立即生效,但重启后失效。

修改 /etc/fstab, 把 swap 相关的那一行注释掉,永久生效。

free -m 确认 swap 相关的都是 0, 说明已经关闭。

设置 hostname

有的资料说最好把虚拟机 hostname 改成 xxx.yy 的形式。

  1. 编辑/etc/hostname,将 hostname 修改为 192-168-56-10.master
  2. sysctl kernel.hostname=new-hostname
  3. 编辑/etc/hosts,追加内容 192-168-56-10.master

最后运行 hostname 确认修改生效。

每台虚拟机上的 hosts 文件都需要修改,以 master 为例,最后应该是这样


$ cat /etc/hosts

127.0.0.1   192-168-56-10.master
192.168.56.11   192-168-56-11.node
192.168.56.12   192-168-56-12.node

安装 Kube 组件

前面的准备工作完成了,到这里我们有 3 台虚拟机,相互都能通过 hostname ping 通,并且都安装了 docker。

现在来安装 kubernetes 相关的三个重要组件 kubelet, kubeadm, kubectl

也是把下面这些命令都运行一遍就 OK 了。

apt-get update && apt-get install -y apt-transport-https

curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -

cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb http://apt.kubernetes.io/ kubernetes-xenial main
EOF

apt-get update

apt-get install -y kubelet kubeadm kubectl

systemctl enable kubelet

systemctl start kubelet

kubeadm 自动安装 k8s

官方提供了 kubeadm 这样一个优秀的工具帮助我们搭建 k8s,方便了不少,否则的话光是手动生成各种证书就要花很多时间了。

kubeadm init --apiserver-advertise-address=192.168.56.10 --pod-network-cidr=22.22.0.0/16
  1. --apiserver-advertise-address 绑定 apiserver 到 master 节点的 Host-Only 适配器的地址,默认是绑到 NAT 的地址上,这样其他机器是永远也访问不到的。
  2. --pod-network-cidr 指定 pod 网络地址空间,使用 flannel 等网络 组件必须使用这个空间
  3. 若执行kubeadm init出错或强制终止,则再需要执行该命令时,需要先执行kubeadm reset重置

init 命令运行成功的话,最后会输出下面这段

Your Kubernetes master has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

kubeadm join 192.168.56.10:6443 --token vtu4u1.upnimfemwgk8zpou \
    --discovery-token-ca-cert-hash sha256:dcfd3261d6e5c315c71c59984d17827c6008c2d563b1133f3e7cfd3214c5c49d

按照上面的提示,拷贝文件(也可以把 config 文件 copy 到所有节点上),在 node 节点上运行 join 命令。

上面的 join 命令可以使用 kubeadm token create --print-join-command重新生成。

当我们改变了 master 节点,比如重新安装 k8s 后,node 节点无法加入到集群,这时在 node 节点上kubeadm reset,然后 join 命令重新加入。

之后,能看到节点都加入了集群,并且状态为 ready。

$ kubectl get node
NAME                   STATUS   ROLES    AGE   VERSION
192-168-56-10.master   Ready    master   15h   v1.17.0
192-168-56-11.node     Ready    <none>   15h   v1.17.0
192-168-56-12.node     Ready    <none>   15h   v1.17.0

安装 flannel 网络

按照参考资料中的安装方式,运行下面这个命令,发现 flannel pod 一直在 crash,而 coredns pod 停在 creatingContainer 状态。

kubectl create -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

所以上面的方法并不靠谱,一番搜索之后,下面这个命令似乎能正常工作。

wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/k8s-manifests/kube-flannel-rbac.yml

kubectl apply -f kube-flannel-rbac.yml

但是是不是能正常工作要等到部署网络相关的 app 之后才知道,先这样吧。

完成

至此,kubenetes 集群的搭建就完成了,我们可以运行几个命令来获取集群的相关信息。

$ kubectl cluster-info
Kubernetes master is running at https://192.168.56.10:6443
KubeDNS is running at https://192.168.56.10:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy


$ kubectl get pod --all-namespaces
NAMESPACE              NAME                                           READY   STATUS             RESTARTS   AGE
default                redis-master-68b5d7cd85-n4hvc                  1/1     Running            0          149m
kube-system            coredns-6955765f44-p7mkd                       1/1     Running            0          15h
kube-system            coredns-6955765f44-ssf5m                       1/1     Running            0          15h
kube-system            etcd-192-168-56-10.master                      1/1     Running            0          15h
kube-system            kube-apiserver-192-168-56-10.master            1/1     Running            0          15h
kube-system            kube-controller-manager-192-168-56-10.master   1/1     Running            0          15h
kube-system            kube-flannel-ds-amd64-dh6tf                    1/1     Running            0          3h35m
kube-system            kube-flannel-ds-amd64-kzfvx                    1/1     Running            0          3h35m
kube-system            kube-flannel-ds-amd64-wxr42                    1/1     Running            0          3h35m
kube-system            kube-proxy-8hlxp                               1/1     Running            0          15h
kube-system            kube-proxy-b59t4                               1/1     Running            0          15h
kube-system            kube-proxy-lsv4v                               1/1     Running            0          15h
kube-system            kube-scheduler-192-168-56-10.master            1/1     Running            0          15h
kubernetes-dashboard   dashboard-metrics-scraper-76585494d8-c7sdp     1/1     Running            0          3h8m
kubernetes-dashboard   kubernetes-dashboard-5996555fd8-tvpbg          0/1     CrashLoopBackOff   39         3h8m

部署一个 Redis 服务

现在有一个 k8s 集群,那么来运行一个小的测试用例吧.

部署一个 Redis ,并在 master 节点上通过 redis client 连接到 redis。

deployment

$ cat redis-master-deployment.yaml
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: redis-master
  labels:
    app: redis
spec:
  selector:
    matchLabels:
      app: redis
      role: master
      tier: backend
  replicas: 1
  template:
    metadata:
      labels:
        app: redis
        role: master
        tier: backend
    spec:
      containers:
      - name: master
        image: k8s.gcr.io/redis 
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
        ports:
        - containerPort: 6379

service

$ cat redis-master-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: redis-master
  labels:
    app: redis
    role: master
    tier: backend
spec:
  ports:
  - port: 6379
    targetPort: 6379
  selector:
    app: redis
    role: master
    tier: backend

kubectl apply 之后,可以看到 default namespace 下,pod 已经在运行。

kubectl  get pod
NAME                            READY   STATUS    RESTARTS   AGE
redis-master-68b5d7cd85-n4hvc   1/1     Running   0          154m

然后通过 port forward 把 pod 的端口映射到 master 节点。

$ kubectl port-forward svc/redis-master 7000:6379
Forwarding from 127.0.0.1:7000 -> 6379
Forwarding from [::1]:7000 -> 6379



$ redis-cli -p 7000
127.0.0.1:7000> set hello world
OK
127.0.0.1:7000> get hello
"world"

参考资料