前言
当我开始学习 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
,到了 Service
的 selector
内,名字必须相同,否则会产生 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 的其他操作。最后用一位大佬的话来作为结语:不跨出第一步,永远毛都不是
。