如何进行minikube部署本地镜像实践与k8s网络架构分析,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。
在 windows10 的 WSL2 里用minikube搭建了一个k8s环境,但是每次拉取镜像都很慢甚至失败(原因大家都懂的), 本文的方案的原理是, 先将镜像在宿主机上f-q拉取到本地, 再由minikube 虚机中的k8s 来拉取本地镜像, 这样即可解决之前的拉取镜像失败,也可提升拉取镜像的速度;
两种方案
把私有镜像仓库docker registry 搭建在宿主机上, k8s从本地宿主机上拉取镜像;
把本地镜像推送到minikube的私有镜像仓库上, k8s从minikube的私有镜像仓库(k8s的本地私有镜像仓库)拉取镜像;
一个完整镜像通常包含应用本身和操作系统,当然还包含需要的依赖软件。
首先准备一个应用。新建一个本文文件,起名叫 app.py,写入下面的内容,实现一个简单的web应用:
from flask import Flask import socket import os app = Flask(__name__) @app.route('/') def hello(): html = "Hello {name}!
" \ "主机名: {hostname}
" return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname()) if __name__ == "__main__": app.run(host='0.0.0.0', port=8082)
在这段代码中,使用 Flask 框架启动了一个 Web 服务器,而它唯一的功能是:如果当前环境中有“NAME”这个环境变量,就把它打印在“Hello”后,否则就打印“Hello world”,最后再打印出当前环境的 hostname。
这个应用的依赖文件requirements.txt存在于与app.py同级目录中,内容是:
$ cat requirements.txt Flask
将这样一个应用在容器中跑起来,需要制作一个容器镜像。Docker使用Dockerfile文件来描述镜像的构建过程。在本文中,Dockerfile内容定义如下:
# FROM指令指定了基础镜像是python:3.6-alpine,这个基础镜像包含了Alpine Linux操作系统和python3.6 FROM python:3.6-alpine # WORKDIR指令将工作目录切换为 /app WORKDIR /app # ADD指令将当前目录下的所有内容(app.py、requirements.txt)复制到镜像的 /app 目录下 ADD . /app # RUN指令运行 pip 命令安装依赖 RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt # EXPOSE指令暴露允许被外界访问的8082端口 EXPOSE 8082 # ENV指令设置环境变量NAME ENV NAME World # CMD指令设置容器内进程为:python app.py,即:这个 Python 应用的启动命令 CMD ["python", "app.py"]
这个Dockerfile中用到了很多指令,把包括FROM、WORKDIR、ADD、RUN、EXPOSE、ENV和CMD。指令的具体含义已经以注释的方式写在了Dockerfile中,大家可以查看。通常我们构建镜像时都会依赖一个基础镜像,基础镜像中包含了一些基础信息,我们依赖基础构建出来的新镜像将包含基础镜像中的内容。
需要再详细介绍一下CMD指令。CMD指定了python app.py为这个容器启动后执行的进程。CMD [“python”, “app.py”] 等价于在容器中执行 “python app.py”。
另外,在使用 Dockerfile 时,还有一种 ENTRYPOINT 指令。它和 CMD 都是 Docker 容器进程启动所必需的参数,完整执行格式是:“ENTRYPOINT CMD”。
默认情况下,Docker 会为你提供一个隐含的 ENTRYPOINT,即:/bin/sh -c。所以,在不指定 ENTRYPOINT 时,比如在我们这个例子里,实际上运行在容器里的完整进程是:/bin/sh -c “python app.py”,即 CMD 的内容就是 ENTRYPOINT 的参数。正是基于这样的原理,Docker 容器的启动进程为实际为 ENTRYPOINT,而不是 CMD。
需要注意的是,Dockerfile 里的指令并不都是只在容器内部的操作。就比如 ADD,它指的是把当前目录(即 Dockerfile 所在的目录)里的文件,复制到指定容器内的目录当中。
更多能在Dockerfile中使用的指令,可以参考官方文档:
https://docs.docker.com/engine/reference/builder/#dockerfile-reference。
根据前面的描述,现在我们的整个应用的目录结构应该如下这样:
$ ls Dockerfile app.py requirements.txt
执行下面的指令可以构建镜像:
$ docker build -f /path/to/Dockerfile -t helloworld . Sending build context to Docker daemon 4.608kB Step 1/7 : FROM python:3.6-alpine ---> 5e7f84829665 Step 2/7 : WORKDIR /app ---> Using cache ---> dbb4a00a8f68 Step 3/7 : ADD . /app ---> fd33ac91c6c7 Step 4/7 : RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt ---> Running in 6b82e863d802 Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple Collecting Flask Downloading https://pypi.tuna.tsinghua.edu.cn/packages/f2/28/2a03252dfb9ebf377f40fba6a7841b47083260bf8bd8e737b0c6952df83f/Flask-1.1.2-py2.py3-none-any.whl (94 kB) Collecting click>=5.1 Downloading https://pypi.tuna.tsinghua.edu.cn/packages/dd/c0/4d8f43a9b16e289f36478422031b8a63b54b6ac3b1ba605d602f10dd54d6/click-7.1.1-py2.py3-none-any.whl (82 kB) Collecting Jinja2>=2.10.1 Downloading https://pypi.tuna.tsinghua.edu.cn/packages/27/24/4f35961e5c669e96f6559760042a55b9bcfcdb82b9bdb3c8753dbe042e35/Jinja2-2.11.1-py2.py3-none-any.whl (126 kB) Collecting itsdangerous>=0.24 Downloading https://pypi.tuna.tsinghua.edu.cn/packages/76/ae/44b03b253d6fade317f32c24d100b3b35c2239807046a4c953c7b89fa49e/itsdangerous-1.1.0-py2.py3-none-any.whl (16 kB) Collecting Werkzeug>=0.15 Downloading https://pypi.tuna.tsinghua.edu.cn/packages/cc/94/5f7079a0e00bd6863ef8f1da638721e9da21e5bacee597595b318f71d62e/Werkzeug-1.0.1-py2.py3-none-any.whl (298 kB) Collecting MarkupSafe>=0.23 Downloading https://pypi.tuna.tsinghua.edu.cn/packages/b9/2e/64db92e53b86efccfaea71321f597fa2e1b2bd3853d8ce658568f7a13094/MarkupSafe-1.1.1.tar.gz (19 kB) Building wheels for collected packages: MarkupSafe Building wheel for MarkupSafe (setup.py): started Building wheel for MarkupSafe (setup.py): finished with status 'done' Created wheel for MarkupSafe: filename=MarkupSafe-1.1.1-py3-none-any.whl size=12629 sha256=1f965945354a52423078c573deb1a8116965e67b2467c3640264d7f02058b06d Stored in directory: /root/.cache/pip/wheels/06/e7/1e/6e3a2c1ef63240ab6ae2761b5c012b5a4d38e448725566eb3d Successfully built MarkupSafe Installing collected packages: click, MarkupSafe, Jinja2, itsdangerous, Werkzeug, Flask Successfully installed Flask-1.1.2 Jinja2-2.11.1 MarkupSafe-1.1.1 Werkzeug-1.0.1 click-7.1.1 itsdangerous-1.1.0 Removing intermediate container 6b82e863d802 ---> d672a00c1a2f Step 5/7 : EXPOSE 8083 ---> Running in b9b2338da3f3 Removing intermediate container b9b2338da3f3 ---> e91da5a22e20 Step 6/7 : ENV NAME World ---> Running in d7e5d19f3eed Removing intermediate container d7e5d19f3eed ---> 4f959f34d486 Step 7/7 : CMD ["python", "app.py"] ---> Running in 99a97bedace0 Removing intermediate container 99a97bedace0 ---> 3bc3e537ebb7 Successfully built 3bc3e537ebb7 Successfully tagged helloworld:latest
其中,-t 的作用是给这个镜像加一个 Tag,即:起一个好听的名字。docker build 会自动加载当前目录下的 Dockerfile 文件,然后按照顺序执行Dockerfile文件中的指令。
上面的命令执行完成后,就生成了一个镜像。可以通过下面的指令查看:
$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE helloworld latest 3bc3e537ebb7 2 minutes ago 103MB
还可以通过 docker inspect helloworld:latest 查看镜像的元信息。
元信息中包含了镜像的全部信息,包括镜像的tag,构建时间,环境变量等。
如果镜像不再需要了,可以通过docker image rm删除镜像。
有了镜像,就可以通过下面的指令来运行镜像得到容器了。
$ docker run -p 8082:8082 helloworld * Serving Flask app "app" (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: off * Running on http://0.0.0.0:8082/ (Press CTRL+C to quit)
上面命令中,镜像名 helloworld 后面,什么都不用写,因为在 Dockerfile 中已经指定了 CMD。否则,我就得把进程的启动命令加在后面:
$ docker run -p 8082:8082 helloworld python app.py
从现在看,容器已经正确启动,我们使用curl命令通过宿主机的IP和端口号,来访问容器中的web应用。
$ curl http://0.0.0.0:8082/Hello World!
主机名: 59b607239c3a
从输出中可以看到容器的ID,容器是基于哪个镜像的启动的,容器中的进程,容器的启动时间及端口映射情况,以及容器的名字。
使用docker inspect 59b607239c3a命令,可以查看容器的元数据,内容非常丰富。
2.1 搭建宿主机私有镜像仓库; sudo docker pull registry sudo docker run -d -p 5000:5000 -v $(pwd):/var/lib/registry --restart always --name registry registry:2 2.2 验证私有镜像仓库 curl http://127.0.0.1:5000/v2/_catalog 2.3 推送镜像到私有镜像仓库 重启docker服务使配置生效: systemctl restart docker 172.19.242.79 是宿主机ip mac 配置路径: 工具栏–> docker –> Preferences –> Daemon –> basic –> Insecure registries 加上一行: 172.19.242.79:5000 sudo vim /etc/docker/daemon.json "insecure-registries" : [ "172.19.242.79:5000" ], "debug" : true 2.4 推送到镜像仓库 sudo docker tag helloworld 172.19.242.79:5000/flaskhelloworld # 标记本地镜像,将其归入某一仓库 sudo docker push 172.19.242.79:5000/flaskhelloworld # 最开始 docker build -t 直接打入指定(远程)仓库可省去上一步 curl -X GET 172.19.242.79:5000/v2/_catalog 2.5 配置minikube 并重启 为了使得minikube 虚机中的k8s 能拉取到宿主机私有镜像 需要上述两项配置 –registry-mirror 和 –insecure-registry sudo minikube start --driver=none --insecure-registry="172.19.242.79:5000" --image-mirror-country cn \ --iso-url=https://kubernetes.oss-cn-hangzhou.aliyuncs.com/minikube/iso/minikube-v1.15.1.iso \ --registry-mirror=https://你注册的aliyun加速地址.mirror.aliyuncs.com 2.6 部署镜像与开放服务接口 sudo kubectl create deployment flaskhelloworld --image=172.19.242.79:5000/flaskhelloworld sudo kubectl expose deployment flaskhelloworld --type=LoadBalancer --port=6789 --target-port=8082 sudo minikube service flaskhelloworld --url #sudo kubectl port-forward flaskhelloworld-6b7695c6df-5hclc 8888:8082 #sudo kubectl create -f flaskhelloworld.yaml #sudo kubectl describe pod flaskhelloworld-pod #sudo kubectl port-forward flaskhelloworld-pod 8888:8080 #sudo kubectl run flaskhelloworld --image=172.19.242.79:5000/flaskhelloworld --port 3000 #sudo kubectl expose pod flaskhelloworld --name flaskhelloworld-http --type=LoadBalancer #sudo minikube service flaskhelloworld-http
sudo minikube service flaskhelloworld |-----------|-----------------|-------------|----------------------------| | NAMESPACE | NAME | TARGET PORT | URL | |-----------|-----------------|-------------|----------------------------| | default | flaskhelloworld | 6789 | http://172.19.242.79:30792 | |-----------|-----------------|-------------|----------------------------| ???? 正通过默认浏览器打开服务 default/flaskhelloworld... ???? http://172.19.242.79:30792
每次访问的主机信息不一样,是因为我们 deployments 后,执行了
kubectl scale -n default deployment flaskhelloworld --replicas=3
集群有3个pod
在Kubernetes中,部署一个Deployment是无法对外进行访问的,就是别的应用程序要想访问部署的Deployment,找不到该怎么去访问,为什么这么讲,因为Deployment一般都是多副本部署的,有可能会分布在不同的节点之上,而且重建Pod IP也会变,重新发布一下也会改变,所以没有办法去固定去访问哪个Pod,即使固定了,其他的Pod也访问不了,要想做到多个Pod都去提供服务,前面就必须要加一个负载均衡,提供一个访问入口,只有访问这个统一入口,才能转发到后端多个Pod上,只要访问这个Cluster IP就能转发到后端的Pod上。
Service
Service定义了Pod的逻辑集合和访问这个集合的策略
Service的引入为解决Pod的动态变化,提供了服务发现和负载均衡
使用CoreDNS解析Service名称
sudo kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE flaskhelloworld LoadBalancer 10.98.106.2406789:30792/TCP 31m flink-jobmanager ClusterIP 10.100.169.253 6123/TCP,6124/TCP,8081/TCP 7d12h flink-jobmanager-rest NodePort 10.103.71.37 8081:30424/TCP 7d12h kubernetes ClusterIP 10.96.0.1 443/TCP 7d12h
因此,我们也可以用集群IP来访问:
services 暴露出去之后,也就是需要让用户去访问,比如搭建一个电商网站,让用户去访问,Ingress相对于Service,是一个互补的状态,Service主要提供了集群内部的访问,也可以暴露一个TCP/UDP的端口,而Ingress主要是一个7层的转发,也就是提供一个统一的入口,只要访问Ingress Controller,就能帮你转发你部署所有的项目,也就是所有的项目都使用域名去访问。
为了进一步了解网络架构,我们也可以直接用 pod ip + port 来访问
sudo kubectl get endpoints serviceName # 查看 Service backend pods(在 Kubernetes 对应多个 endpoint)
看完上述内容,你们掌握如何进行minikube部署本地镜像实践与k8s网络架构分析的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注编程笔记行业资讯频道,感谢各位的阅读!