实验描述
此次我使用Django+mysql写了两个接口,然后制作成容器镜像,上传到阿里云的镜像仓库;然后分别演示在docker环境和k8s环境下如何部署。
制作镜像
1.编写Dockerfile
FFROM python:3.13-slim #基于python:3.13-slim镜像
WORKDIR /app #在容器内创建/app目录
#因为我的django要连接mysql,要安装mysql-client,所以容器操作系统要安装一些依赖
RUN apt update && \
apt install -y python3-dev default-libmysqlclient-dev build-essential pkg-config
COPY . /app/ #将本地的程序拷贝到/app/目录下
#安装python库
RUN pip install -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple -r requirements.txt
EXPOSE 8000 #8000端口
#启动命令
CMD [ "python", "manage.py", "runserver", "0.0.0.0:8000" ]
2.buid镜像
#我的Dockerfile文件就在项目的根目录下
04:02:06 zh@fedora demo → ls
demo Dockerfile manage.py requirements.txt users
在该目录下执行命令: docker build -t demo .
像下面这样输出,就表示镜像做好了。
03:47:35 (venv) zh@fedora demo → sudo docker build -t demo .
[+] Building 80.3s (10/10) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 357B 0.0s
=> [internal] load metadata for docker.io/library/python:3.13-slim 1.9s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/5] FROM docker.io/library/python:3.13-slim@sha256:9ed09f78253eb4f029f3d99e07c064f138a6f1394932c3807b3d0738a674d33b 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 1.90kB 0.0s
=> CACHED [2/5] WORKDIR /app 0.0s
=> [3/5] RUN apt update && apt install -y python3-dev default-libmysqlclient-dev build-essential pkg-config 55.5s
=> [4/5] COPY . /app/ 0.0s
=> [5/5] RUN pip install -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple -r requirements.txt 21.8s
=> exporting to image 1.0s
=> => exporting layers 1.0s
=> => writing image sha256:4cbd82bb6f03a10f7ba6b01dbdeeb3aeda8fa0e2621b53cb721e36c8d34a5a62 0.0s
=> => naming to docker.io/library/demo
3.查看镜像
04:10:26 zh@fedora demo → sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
demo latest 4cbd82bb6f03 20 minutes ago 654MB
mysql latest edbdd97bf78b 8 weeks ago 859MB
#上面的demo:latest就是我们刚刚做好的镜像
3.测试
因为我这里是通过环境变量来连接数据库的,所以我先创建了一个.env文件,然后在运行容器。
04:12:57 zh@fedora ~ → cat .env
DB_NAME=mydjangodb
DB_USER=test
DB_PASSWORD=centos
DB_HOST=192.168.66.110
DB_PORT=3306
指定环境变量文件运行容器
sudo docker run --name demo --env-file .env -p 8000:8000 -d demo:latest
#下面的demo容器就是刚刚运行的。
04:15:07 zh@fedora ~ → sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9ac047d44181 demo:latest "python manage.py ru…" 18 minutes ago Up 18 minutes 0.0.0.0:8000->8000/tcp, [::]:8000->8000/tcp demo
dd2364c1728a mysql:latest "docker-entrypoint.s…" 30 hours ago Up 7 hours 0.0.0.0:3306->3306/tcp, [::]:3306->3306/tcp, 33060/tcp mysql
访问创建用户接口,然后在访问获取用户列表接口,确保能正常连接数据库(数据库提前准备好)。
#创建用户
curl -X 'POST' \
'http://localhost:8000/api/user/create' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"name": "刘华强",
"age": 22,
"sex": "M"
}'
#查看刚创建的用户
curl -X 'GET' \
'http://localhost:8000/api/user/list' \
-H 'accept: application/json'
#返回如下
[
{
"id": 5,
"name": "刘华强",
"age": 22,
"sex": "M"
}
]
此时说明我们制作的镜像是没问题的。
4.上传镜像到阿里云镜像仓库
创建一个私有仓库
登录仓库
04:41:46 zh@fedora ~ → sudo docker login --username=刘华强的同学 registry.cn-hangzhou.aliyuncs.com
Password:
WARNING! Your credentials are stored unencrypted in '/root/.docker/config.json'.
Configure a credential helper to remove this warning. See
https://docs.docker.com/go/credential-store/
Login Succeeded
#登录成功
#打标签
sudo docker tag demo:latest registry.cn-hangzhou.aliyuncs.com/shuiku/dj_demo:latest
#上传
04:44:06 zh@fedora ~ → sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
demo latest 4cbd82bb6f03 53 minutes ago 654MB
registry.cn-hangzhou.aliyuncs.com/shuiku/dj_demo latest 4cbd82bb6f03 53 minutes ago 654MB
mysql latest edbdd97bf78b 8 weeks ago 859MB
04:44:09 zh@fedora ~ → sudo docker push registry.cn-hangzhou.aliyuncs.com/shuiku/dj_demo:latest
The push refers to repository [registry.cn-hangzhou.aliyuncs.com/shuiku/dj_demo]
243bef3e6e49: Pushed
2bc255544a51: Pushed
06326f99ae05: Pushed
313f5b178018: Pushed
57fd89a469c4: Pushed
e3d3e0aca184: Pushed
e260854ac349: Pushed
7fb72a7d1a8e: Pushed
latest: digest: sha256:1ae6a8a9a6221ae2b29e0125486c200edd8a6c879d66d7b76fe9e6064253c50a size: 1998
#上传成功,可以在阿里云镜像仓库页面看到
使用docker compose运行django demo和mysql
.env文件需要和docker-compose.yaml在同一目录下面
1.更改环境变量文件.env
在docker compose中,django容器连接mysql是通过服务名称来连接的,所以我们要更改一下.env内容
# 给 MySQL 容器使用
MYSQL_DATABASE=mydjangodb
MYSQL_USER=test
MYSQL_PASSWORD=centos
MYSQL_ROOT_PASSWORD=centos
# 给 Django 容器使用
DB_NAME=mydjangodb
DB_USER=test
DB_PASSWORD=centos
DB_HOST=db #下面mysql服务名称设置为db
DB_PORT=3306
2.编写docker compose
services:
db:
image: mysql:latest
container_name: mysql1
env_file:
- .env # MySQL 读取 .env
ports:
- "3306:3306"
restart: unless-stopped
web:
image: registry.cn-hangzhou.aliyuncs.com/shuiku/dj_demo:latest
container_name: demo1
env_file:
- .env
depends_on:
- db
ports:
- "8000:8000"
#这里的command会替代制作容器镜像里的CMD,因为要先等mysql启动,所以这里等待了5秒。
command: >
sh -c 'echo "5 seconds..."; sleep 5; python manage.py migrate && python manage.py runserver 0.0.0.0:8000'
restart: unless-stopped
3.启动容器并测试
#执行如下命令启动mysql容器和demo(django)容器
11:27:28 zh@fedora tmp → sudo docker compose up -d
[+] Running 2/2
✔ Container mysql1 Started 0.1s
✔ Container demo1 Started
访问创建用户信息接口
curl -X POST http://localhost:8000/api/user/create -H "Content-Type: application/json" -d '{"name":"cxk","age":66,"sex":"M"}'
#返回如下,代表没问题
{"id": 2, "name": "cxk", "age": 66, "sex": "M"}
访问获取用户接口
curl localhost:8000/api/user/list
#返回如下
[{"id": 2, "name": "cxk", "age": 66, "sex": "M"}]
部署应用到k8s集群
这里我为方便使用了k3s,命令、yaml都是一样的。
1.安装k3s
直接使用官网提供的脚本,一键安装。
#这个地址可能需要🪜
curl -sfL https://get.k3s.io | sh -
#国内可以直接用这个地址
curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn sh -
然后等待安装完成即可
查看所有pod运行成功就好了
[root@test ~]# kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-697968c856-rjszk 1/1 Running 0 4m55s
kube-system helm-install-traefik-crd-t9z7l 0/1 Completed 0 4m55s
kube-system helm-install-traefik-drknh 0/1 Completed 1 4m55s
kube-system local-path-provisioner-774c6665dc-7s7j9 1/1 Running 0 4m55s
kube-system metrics-server-6f4c6675d5-mfc58 1/1 Running 0 4m55s
kube-system svclb-traefik-dcc55195-dhb9f 2/2 Running 0 4m14s
kube-system traefik-c98fdf6fb-9tddv 1/1 Running 0 4m14s
2.配置kubectl命令补全
#我这里用的操作系统是Rocky linux 9.4
dnf install bash-completion
echo 'source <(kubectl completion bash)' >> ~/.bashrc
source ~/.bashrc
3.编写deployment yaml文件
首先通过命令生成一个deployment yaml文件,然后在这个基础上更改。
deployment是工作负载,它可以一直保持pod副本数量和我们设置的一致。
#通过help命令可以查看创建deployment的示例,还有参数。
[root@test ~]# kubectl create deployment --help
Create a deployment with the specified name.
Aliases:
deployment, deploy
Examples:
# Create a deployment named my-dep that runs the busybox image
kubectl create deployment my-dep --image=busybox
# Create a deployment with a command
kubectl create deployment my-dep --image=busybox -- date
# Create a deployment named my-dep that runs the nginx image with 3 replicas
kubectl create deployment my-dep --image=nginx --replicas=3
# Create a deployment named my-dep that runs the busybox image and expose port 5701
kubectl create deployment my-dep --image=busybox --port=5701
# Create a deployment named my-dep that runs multiple containers
kubectl create deployment my-dep --image=busybox:latest --image=ubuntu:latest --image=nginx
Usage:
kubectl create deployment NAME --image=image -- [COMMAND] [args...] [options]
通过示例命令我们就可以创建自己的deployment
kubectl create deployment web --image=registry.cn-hangzhou.aliyuncs.com/shuiku/dj_demo:latest --port=8000 --replicas=3 --dry-run=client -o yaml > web.yaml
# --dry-run=client 这个的参数意思是这条命令不会真正的执行,相当于测试、预期。
# --replicas=3 指定pod副本数为3
# --port=8000 指定容器端口
# -o yaml > web.yaml 将这条命令一yaml格式输出到web.yaml文件中,方便后面在这个文件的基础上更改。
更改后的web.yaml文件
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- image: registry.cn-hangzhou.aliyuncs.com/shuiku/dj_demo:latest
name: dj-demo-xpmxp
ports:
- containerPort: 8000
env: #连接数据库需要的环境变量,数据库提前准备好
- name: DB_NAME
value: "mydjangodb"
- name: DB_USER
value: "test"
- name: DB_PASSWORD
value: "centos"
- name: DB_HOST
value: "192.168.137.29"
- name: DB_PORT
value: "3306"
#要执行的命令,迁移数据到数据库,启动django服务
command: ["/bin/sh", "-c"]
args: ["python manage.py migrate && python manage.py runserver 0.0.0.0:8000"]
如果此时直接kubectl apply -f web.yaml,会拉不下镜像来,因为创建的是阿里云私有镜像仓库,需要登录,所以我们还得创建secret,用于登录。
还是通过kubectl帮助文档查看secret怎么创建
[root@test ~]# kubectl create secret --help
Create a secret with specified type.
A docker-registry type secret is for accessing a container registry.
A generic type secret indicate an Opaque secret type.
A tls type secret holds TLS certificate and its associated key.
Available Commands:
docker-registry Create a secret for use with a Docker registry
generic Create a secret from a local file, directory, or literal value
tls Create a TLS secret
Usage:
kubectl create secret (docker-registry | generic | tls) [options]
可以看到docker-registry是关于docker的密钥,继续help
kubectl create secret docker-registry --help
......
Usage:
kubectl create secret docker-registry NAME --docker-username=user --docker-password=password --docker-email=email
[--docker-server=string] [--from-file=[key=]source] [--dry-run=server|client|none] [options]
我们照着上面的使用方法写就好了,如下
kubectl create secret docker-registry dj-secret \
--docker-server=registry.cn-hangzhou.aliyuncs.com \
--docker-username=你的阿里云账号 \
--docker-password=你的阿里云密码或ACR临时密码 \
查看登录secret
[root@test ~]# kubectl get secret
NAME TYPE DATA AGE
dj-ecret kubernetes.io/dockerconfigjson 1 46s
再次修改web.yaml,添加imagePullSecrets
kind: Deployment
metadata:
labels:
app: web
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- image: registry.cn-hangzhou.aliyuncs.com/shuiku/dj_demo:latest
name: dj-demo-xpmxp
ports:
- containerPort: 8000
env:
- name: DB_NAME
value: "mydjangodb"
- name: DB_USER
value: "test"
- name: DB_PASSWORD
value: "centos"
- name: DB_HOST
value: "192.168.137.29"
- name: DB_PORT
value: "3306"
command: ["/bin/sh", "-c"]
args: ["python manage.py migrate && python manage.py runserver 0.0.0.0:8000"]
#下面这两行是新增的
imagePullSecrets:
- name: dj-secret
4.应用web.yaml文件
kubectl apply -f web.yaml
#查看pod运行是否正常
[root@test ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
web-5bd7b59bd5-9bg5p 1/1 Running 5 (2m24s ago) 5m15s
web-5bd7b59bd5-rdnbw 1/1 Running 5 (2m24s ago) 5m15s
web-5bd7b59bd5-zsgww 1/1 Running 5 (2m27s ago) 5m15s
5.创建service
service是提供了一个统一访问入口,并且有负载均衡和服务发现功能
它会将请求均匀的分发到我们刚刚创建的这3个pod上,如果副本数发生变换,比如我们要扩容,service也会发现的,请求也会往新的副本上发送的,这一切都不用人工干预。
kubectl expose deployment web --port=8000 --protocol=TCP --target-port=8000 --type=NodePort --dry-run=client -o yaml >> web.yaml
# --port 是service暴露的端口
# --target-port 是pod的端口
# --type=NodePort 允许通过节点和一个30000以后的端口访问到该service
#ClusterIP(默认)
#仅集群内部访问,自动分配虚拟IP,实现服务发现和负载均衡。
#NodePort
#在 ClusterIP 基础上,在每个节点上开放静态端口(30000-32767),支持从集群外部通过 节点IP:端口 访问。
#LoadBalancer
#在 NodePort 基础上,集成云厂商的负载均衡器(如 AWS ALB、阿里云 SLB),直接提供外部可访问的IP/域名。
#一句话总结:
#ClusterIP 对内,NodePort 暴露节点端口,LoadBalancer 走云服务商。
然后对该service的yaml文件作修改,我是和deployment放一个yaml了。
apiVersion: v1
kind: Service
metadata:
labels:
app: web
name: web
spec:
ports:
- port: 8000
protocol: TCP
targetPort: 8000
selector:
app: web
type: NodePort
应用service
[root@test ~]# kubectl apply -f web.yaml
deployment.apps/web unchanged
service/web created
[root@test ~]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 129m
web NodePort 10.43.6.163 <none> 8000:32435/TCP 4s
#名称为web的service就是我们刚刚创建的
[root@test ~]# kubectl describe service web
Name: web
Namespace: default
Labels: app=web
Annotations: <none>
Selector: app=web
Type: NodePort
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.43.6.163
IPs: 10.43.6.163
Port: <unset> 8000/TCP
TargetPort: 8000/TCP
NodePort: <unset> 32435/TCP
#下面的endpoints正好对应的是3个pod的ip地址
Endpoints: 10.42.0.21:8000,10.42.0.22:8000,10.42.0.23:8000
Session Affinity: None
External Traffic Policy: Cluster
Internal Traffic Policy: Cluster
Events: <none>
[root@test ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-5bd7b59bd5-9bg5p 1/1 Running 5 (13m ago) 16m 10.42.0.23 test <none> <none>
web-5bd7b59bd5-rdnbw 1/1 Running 5 (13m ago) 16m 10.42.0.21 test <none> <none>
web-5bd7b59bd5-zsgww 1/1 Running 5 (13m ago) 16m 10.42.0.22 test <none> <none>
访问service地址,节点ip:32435测试
curl -X POST http://localhost:32435/api/user/create -H "Content-Type: application/json" -d '{"name":"zhangsan","age":28,"sex":"M"}'
#下面就是接口返回的,说明通过service是可以正常访问的
{"id": 6, "name": "zhangsan", "age": 28, "sex": "M"}
总结
还有ingress也是提供访问入口的,写不动了 ,先到这吧,后续有时间还会写一些运维相关的东西,比如日志收集、k8s监控等,感兴趣也可以关注我的公众号:运维张三