从零开始 K8S

前言

当我开始学习 K8S 时,我发现网上居然很难得有一篇能把方方面面都讲得清楚,并且能轻松领进门的文章。作为一个新人,我很难看明白那些炫技的东西,也很难看明白各种术语,同样的也不知道坑在哪里,如何踩过去。找了一些文章并按着操作后,重新建了几次环境不说,还没能把 Pod 跑起来,最后在张瑾大神的帮助下,完成了所有的操作。因此还是有必要写一篇来唠唠这事,把完整的步骤记录下来。


什么是 K8S ?

Kubernetes 是一个用于容器集群的自动化部署、扩容以及运维的开源平台。至于为什么叫它 K8S,是因为 K 和 S 中间刚好 8 个字母,而且那单词比较难记,就简称 K8S 了。

K8S 的初衷是培育出一个组件及工具的生态,帮助大家减轻在云上运行应用的负担,换言之,使得大型分布式应用的构建和运维变得更加简单。


搭建环境

这一步我个人尝试了好几回,由于 K8S 版本的更新,以及系统的更新,网上很多教程已不再适合使用,对于现在才初学的人造成了不少的困扰。我采用的环境是 Ubuntu 20.04 Server,并在此基础上进行搭建。

首先在虚拟机里装好 Ubuntu,这一步不说了,虚拟机选用的是 Parallel Desktop 16.1.1,在安装完成系统后,安装一下 Parallels Tools。需要说明的是,Ubuntu Server 版没有图形界面,插入 iso 后也不会自动挂载到 /media 下,需要进行手动操作,如下:

$ sudo mkdir -p /media/cdrom
$ sudo mount -o exec /dev/cdrom /media/cdrom
$ cd /media/cdrom
$ sudo ./install

等待 Parallels Tools 安装完毕,重启即可。


下面来进行 K8S 环境的搭建,首先修改计算机名称,修改名称只是为了方便查找和操作节点,这是一个可选的操作,对于初学者来说,还是注意一下比较好,我把虚拟机的名字改成了 k8s-master

$ sudo hostnamectl set-hostname "k8s-master"

由于我只想单机部署,因此只设置 master 就可以了,如果有其他节点,也尽可能将计算机名改成识别度较高的。

查看本机的 IP 地址,并且加入到 /etc/hosts 内,如下:

$ ip addr

得到我本机的 IP 为 10.211.55.16,那么加入 hosts 就是这样的:

10.211.55.16 k8s-master

完成后安装 Docker:

$ sudo apt update
$ sudo apt install docker.io

需要注意的是,这里尽可能的更换 apt 源为国内源,否则有可能安装时非常之慢。

安装完成后需要将 Docker 服务设置为开机自启:

$ sudo systemctl enable docker.service --now

完成后可以查看 Docker 服务的状态,以及查看服务的版本:

$ systemctl status docker
$ docker --version

目前 Ubuntu 20.04 APT 源内带的 Docker 的版本号为 19.03.8


接下来要关闭交换分区,并开启 IP 转发。关闭交换分区需要修改 fstab:

$ sudo vim /etc/fstab

此时将会看到一个名为 /swap.img 的分区,使用 # 将这整行注释掉,然后保存。再执行关闭命令:

$ sudo swapoff -a

现在可以开启 IP 转发功能。编辑 sysctl.conf 文件:

$ sudo vim /etc/sysctl.conf

在这个文件里,我们可以找到一句被注释掉的 net.ipv4.ip_forward=1,将它前面的 # 去掉并保存文件。

完成后使用以下命令来查看 IP 转发是否已被开启:

$ sudo sysctl -p

若是看到输出为 net.ipv4.ip_forward = 1 则表示 IP 转发已开启。


安装 K8S 工具,到了这一步,坑就慢慢出现了,需要细细品味。

$ sudo apt install -y apt-transport-https curl
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add
$ sudo apt-add-repository "deb http://apt.kubernetes.io/ kubernetes-xenial main"
$ sudo apt update
$ sudo apt install -y kubelet kubeadm kubectl

正常的流程是这样的,从 google 拿一个 gpg key,并且添加 K8S 的 APT 源,但是偏偏在 hard 模式下,从 google 拿东西就是拿不到,还是只能改为国内源:

$ sudo apt install -y apt-transport-https curl
$ curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add
$ sudo apt-add-repository "deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main"
$ sudo apt update
$ sudo apt install -y kubelet kubeadm kubectl

然后需要用 kubeadm 来进行初始化,同样的需要加入国内源:

$ sudo kubeadm init --image-repository="registry.cn-hangzhou.aliyuncs.com/google_containers"

这一步非常的坑,如果用官方源,你会发现无论是否爬梯,都很难完成这一步,还好阿里云提供了方便,完成后执行以下代码:

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

然后编辑 .bashrc 将环境变量导出:

$ vim ~/.bashrc

在文件最后加入代码:

export KUBECONFIG=$HOME/.kube/config

现在可以执行 kubectl 命令,来查看节点的状态了:

$ kubectl get nodes

此时将看到 k8s-master 节点是 NotReady 的状态,这表示 K8S 工具已经能正常工作了,但是节点还未准备好。


下面来安装 Calico,它是一个网络插件,安装完成后,节点就会变成 Ready 的状态,但是安装这个东西依然很坑。

我们需要先修改 Docker 的源为国内源:

$ sudo vim /etc/docker/daemon.json

可以发现这个文件原本是不存在的,我们加入国内源的内容,然后保存即可:

{
    "registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"]
}

完成后重新启动 Docker 服务,以使得源被加载:

$ sudo systemctl restart docker

然后再进行 Calico 的安装:

$ kubectl apply -f https://docs.projectcalico.org/v3.14/manifests/calico.yaml

换源之后安装还是非常快的,大概 2 分钟就能完工,如果用官方源,会发现相关的 pod 永远在 Init 状态,使用 kubectl describe pod 命令进行查看时,将发现卡在 Pull Image 过程中。

对于卡住的情况,可以使用以下命令先将 Calico 删除,然后再重新安装:

$ kubectl delete -f https://docs.projectcalico.org/v3.14/manifests/calico.yaml

Calico 安装完毕后,再使用 kubectl get nodes 命令,将可以看到节点已经是 Ready 状态。


部署容器

刚才我们已经完成了环境的搭建,下面就是要把自己开发的应用部署到 K8S 内了,这个过程不难,但是依然蛋疼。

首先我们要写好一个服务端应用,关于服务程序本身,这里我简单的略过了,总之就是拿 Ktor 随便写了两行代码,然后打成 jar 包,这个 jar 包可以使用 java -jar 命令予以启动,并且服务启动后监听 9001 端口。

然后我们来写一个最简单的 Dockerfile,将服务打成镜象:

FROM openjdk:8-jdk-alpine

ARG app=sample-service
ARG path=/home
ARG Jar=$app.jar
ARG JAR_HOME=$path/$app
ARG config=$JAR_HOME/config
ARG data=$JAR_HOME/data

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
    && apk add tzdata \
    && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtme \
    && echo "Asia/Shanghai" > /etc/timezone \
    && apk del tzdata \
    && apk add dumb-init \
    && mkdir -p $JAR_HOME \
    && mkdir -p $config \
    && mkdir -p $data \
    && mkdir -p /sidecar/agent/

COPY $Jar $JAR_HOME
WORKDIR $JAR_HOME
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["java", "-jar", "sample-service.jar"]

这里我事先申请好了 Docker Hub 的帐号,并且建立了一个 repository,这样可以直接把构建出来的镜象 push 到 Docker Hub:

$ sudo docker build -t rarnu/sample-service .

完成后可以执行以下命令,来测试这个镜象是否好用:

$ sudo docker run rarnu/sample-service

测试无误,就可以将镜象 push 到 Docker Hub 上去了:

$ sudo docker push rarnu/sample-service

此时 Docker 会帮我们打一个名为 latest 的 tag,后面会用到。


现在我们有镜象了,就可以把镜象部署到 K8S 里,这里需要编写一个 yaml 描述文件:

apiVersion: v1
kind: Pod
metadata:
  name: sample-service
  labels:
    app: sample-service
spec:
  containers:
    - name: sample-service-container
      image: rarnu/sample-service:latest
      ports:
        - containerPort: 9001
      imagePullPolicy: IfNotPresent
---
apiVersion: v1  
kind: Service 
metadata:  
  name: sample-service
  labels:  
    app: sample-service 
spec:
  externalTrafficPolicy: Cluster
  ports: 
  - port: 9001
    targetPort: 9001
    protocol: TCP
    nodePort: 32001
  type: NodePort
  selector:  
    app: sample-service

需要注意的是,Pod 下的 containerPort 对应的是容器内部占用的端口号,而 Service 下的 nodePort 则是外部访问时所用的端口号,它将通过 kube-proxy 服务进行转发。

另外,需要注意的是 label,在 Pod 内写的 label,如 app: sample-service,到了 Serviceselector 内,名字必须相同,否则会产生 Service 与 Pod 不能对应的问题。

有了这个 yaml 后,我们依此来创建 Pod 和 Service:

$ kubectl create -f sample-service.yaml

部署会需要一些时间,取决于镜象拉取的速度,部署完成后可以用以下命令查看状态:

$ kubectl get pods
$ kubectl get services

这里有一个比较大的坑,即 master 节点默认不参与 Pod 调度,因此直接 kubectl create 即使成功创建了 Pod,也不会按 yaml 文件的描述去拉取镜象,在此需要额外执行一个命令:

$ sudo kubectl taint node k8s-master node-role.kubernetes.io/master

此处的 k8s-master 即是我们之前定义好的虚拟机名称。在此后,master 节点就将参与 Pod 调度,也即是说 kubectl create 会真正生效。


访问部署好的服务

部署好服务之后,就需要访问它了,这里的访问分为三部分,在 Pod 内访问,在宿主机访问,和在外部访问。在 Pod 内访问可以这样做:

$ kubectl exec -it sample-service sh
# apk add curl
# curl 'http://0.0.0.0:9001' 

此时即可看到结果,在宿主机访问也同样简单,只需要知道 Pod 的 IP:

$ kubectl describe pod sample-service

此时显示的 IP 即是 Pod 的 IP,使用它来访问即可:

$ curl 'http://192.168.235.200:9001'

需要注意的是,无论是在 Pod 内,还是在宿主机上访问,均使用本地端口,即 9001,而在外部访问,则需要用远程端口,即 32001。在另一台局域网内的电脑上访问 http://10.211.55.16:32001 将会产生错误,原因是 kube-proxy 采用 iptables 进行管理,并且默认不会开启所有端口,因此我们需要做一些事情来将它开启:

$ sudo apt install iptables-persistent
$ sudo iptables -I INPUT -s 0.0.0.0/24 -p tcp --dport 32001 -j ACCEPT
$ sudo iptables-save
$ sudo netfilter-persistent save
$ sudo netfilter-persistent reload

此时再用外部机器就可以访问了,在浏览器使用地址 http://10.211.55.16:32001 即可看到效果。

错误排查

用张瑾大神的话来说,就是一切问题都可以通过 kubectl describe 命令来排查,在整个部署过程中,遇到的最多的问题就是网络问题。作为一款生产力工具,大局域网却如此不给面子,故意增加技术难度,着实有一种有苦说不出的感觉。所以每一步操作,都尽可能的去检查源是否国内的,或者进行代理,这样才能进行稍顺利些。

这只是最基础的 K8S 上手,后续也将继续学习 K8S 的其他操作。最后用一位大佬的话来作为结语:不跨出第一步,永远毛都不是

你可能感兴趣的:(从零开始 K8S)