容器化部署应用基本流程

实验描述

此次我使用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也是提供访问入口的,写不动了 :laughing:,先到这吧,后续有时间还会写一些运维相关的东西,比如日志收集、k8s监控等,感兴趣也可以关注我的公众号:运维张三

36 Likes

学习到了

是机器大哥吗

方块 AI 为您服务

感谢佬友分享 :folded_hands:

牛逼牛逼

很细致哦!

谢谢,感觉这次文字描述的有点太少了 :laughing:

1 Like

:laughing: :laughing: :laughing: :laughing:

1 Like

感谢大佬教程!

问题不大,反正我本来也会,就不管别人了

学习一下!

学习到了佬,下次讲讲云上k8s部署

学习学习了

感谢分享

感谢大佬分享

感谢大佬,到我的笔记里吃灰吧!

大佬很强

太详细了,感谢大佬分享

感谢佬的分享