当前位置:   article > 正文

ubuntu 卸载docker_Docker

ubuntu docker remove

Docker: 码头工人 (搬砖高手)

Life is short. We use Docker.

版本:Ubuntu 16.04 LTS,Docker 19.03.1, build 74b1e89e8a

安装 docker

  1. # 本节参考 Docker 官方文档:
  2. # https://docs.docker.com/install/linux/docker-ce/ubuntu/
  3. # 卸载旧版本
  4. sudo apt-get remove docker docker-engine docker.io containerd runc
  5. sudo apt-get update
  6. sudo apt-get install apt-transport-https ca-certificates gnupg-agent software-properties-common
  7. # 添加 Docker 的官方 GPG 密钥
  8. curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
  9. # 设置稳定存储库
  10. sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
  11. sudo apt-get update
  12. sudo apt-get install docker-ce docker-ce-cli containerd.io
  13. # 测试
  14. sudo docker run hello-world

执行步骤

Docker 的 5 个基本部件:Docker Client,Docker Daemon,Docker Image,Docker Container,Docker Hub (Registry)。

  1. Docker Client 联系 Docker Daemon。
  2. Docker Daemon 从 Docker Hub 中提取名为 hello-world 的 Docker Image。
  3. Docker Daemon 从该 Docker Image 创建了一个 Docker Container,用于生成当前输出。
  4. Docker Daemon 将该输出流式传输到 Docker Client。

f542f4b838f97007ab0444a0f45af2ef.png

卸载 docker

  1. sudo apt-get purge docker-ce
  2. # 删除相关文件
  3. sudo rm -rf /var/lib/docker

基本命令

  1. docker --version
  2. sudo docker info

USER 加入 docker 组

  1. # 用户访问 Docker daemon,需要管理员权限 sudo,或加入 docker 组
  2. # Docker 安装完成会建立 docker 组
  3. cat /etc/group | grep docker
  4. cat /etc/group | sed -n '/docker/p' | awk -F ":" '{print $1}'
  5. # 加入 docker 组
  6. sudo usermod -aG docker $USER
  7. # 重启
  8. echo -e "USER_PASSWDn" | sudo -S reboot
  9. # 检查是否已加入 docker 组
  10. groups | grep docker

常用命令

  1. # 详细版本信息
  2. docker version
  3. # 列出 image
  4. docker image ls --all
  5. docker images
  6. docker images -a
  7. # 列出 container
  8. docker container ls
  9. docker container ls --all
  10. docker container ls -aq
  11. # 搜索
  12. docker search python
  13. docker search anaconda
  14. # 显示运行的容器
  15. docker ps
  16. docker ps -a

ecda74033b16c6f60fc0fa6773aa2003.png

上图介绍了基本的 docker 命令流,我们从一个小例子开始。

  1. docker pull busybox
  2. docker images
  3. docker run busybox pwd
  4. docker run busybox echo 'I love Python'

docker pull + docker run 能够让我们迅速、便捷地运行一个 docker image。

dock pull

格式:docker pull [OPTIONS] NAME[:TAG|@DIGEST]

常用参数

  1. -a, --all-tags # 下载 repository 中所有 tagged images
  2. --disable-content-trust # 跳过镜像验证 (默认跳过)
  3. -q, --quiet # 取消进度显示

docker run

格式:docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

常用参数:

  1. -it # -i 交互模式,-t 分配伪 tty
  2. -P # 随机端口映射
  3. -p # 指定端口映射,主机端口:容器端口
  4. --name # 指定容器名称
  5. -e, --env list # 设置环境变量
  6. -v, --volume list # 本地文件夹与 container 文件夹绑定
  7. -w # 容器内的工作目录
  8. --dns # 设置 DNS 服务器
  9. -h, --hostname string # 设置容器的 hostname;
  10. --rm # 自动移除容器
  11. --env-file list # 从文件读入环境变量
  12. -d # 后台运行容器
  13. -m # 设置容器内存上限
  14. --link list # 链接到其他容器

拉取并执行镜像

  1. # 通过示例展示 docker run 的用法
  2. # 例一:busybox
  3. docker pull busybox
  4. # -it 表示可以交互式地输入命令
  5. docker run -it busybox sh
  6. # 例二:tensorflow
  7. docker pull tensorflow/tensorflow
  8. # --rm 在容器退出时自动清理容器并删除文件系统
  9. # 默认 python=2.7,tensorflow=1.14.0
  10. docker run -it --rm tensorflow/tensorflow
  11. # 退出后运行
  12. docker container ls --all #不显示 tensorflow,说明 --rm 功能
  13. # 定制 python=3.5,tensorflow=1.11.0
  14. docker pull tensorflow/tensorflow:1.11.0-rc2-py3
  15. # --name 指定 container 名称 [a-zA-Z0-9][a-zA-Z0-9_.-]
  16. # 命令最后指定 python 解释器,后面可以接 .py 文件
  17. docker run -it --rm --name tf_11-py_35 tensorflow/tensorflow:1.11.0-rc2-py3 python
  18. # -v 将本地目录映射 container 目录
  19. # $PWD:/dl 将本地当前目录 $PWD,挂载到 container 的 /dl 目录下
  20. # -v 命令可以实现数据的完全共享、读写同步
  21. # 如果想要禁止 container 的写权利,可以 -v $PWD:/dl:ro
  22. docker run -it --rm --name tf_11-py_35 -v $PWD:/dl tensorflow/tensorflow:1.11.0-rc2-py3 sh # bash 窗口操作
  23. # -p 指定端口映射,可以在本地 http://127.0.0.1:8888 访问 container
  24. # 本地的 $HOME 目录将被挂载到 /tf 目录下
  25. docker run -it --rm -v $HOME:/tf -p 8888:8888 tensorflow/tensorflow:nightly-py3-jupyter
  26. # 例三:jupyter
  27. # 将 container 的 8888 端口映射到本机 127.0.0.1666 端口,
  28. # http://localhost:666/
  29. docker run -p 127.0.0.1:666:8888 jupyter/scipy-notebook:17aba6048f44

4e3983487b20703013f6e5cbcec20f54.png

删除 Container,Image

  1. # 删除 container
  2. # 用 CONTAINER ID 或名字
  3. docker rm c1b9ce7f84d5
  4. docker rm tf_11-py_35
  5. docker rm -f $(docker ps -aq)
  6. # 删除所有状态为 exited 的容器
  7. docker rm $(docker ps -a -q -f status=exited)
  8. # 删除镜像 [需要删除镜像的容器]
  9. # 用 IMAGE ID 或 REPOSITORY:TAG
  10. docker rmi [OPTIONS] IMAGE [IMAGE...]

容器操作

  1. # 启动 container
  2. docker start d3c0ea01414d
  3. # 停止 container
  4. docker stop d3c0ea01414d
  5. # kill container
  6. docker kill -s kill d3c0ea01414d
  7. # 重启 container
  8. docker restart d3c0ea01414d
  9. # commit
  10. docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]

创建镜像

首先是 docker 的核心之一,Dockerfile —— 制作 Docker Image 的说明书。

e43f0b507ef716c564ced71158358472.png

Dockerfile

先看三个小例子

例一:ubuntu 16.04 LTS (xenial) 官方 Dockerfile

  1. FROM ubuntu:xenial
  2. RUN apt-get update && apt-get install -y --no-install-recommends
  3. ca-certificates
  4. curl
  5. netbase
  6. wget
  7. && rm -rf /var/lib/apt/lists/*
  8. RUN set -ex;
  9. if ! command -v gpg > /dev/null; then
  10. apt-get update;
  11. apt-get install -y --no-install-recommends
  12. gnupg
  13. dirmngr
  14. ;
  15. rm -rf /var/lib/apt/lists/*;
  16. fi

例二:jupyter minimal-notebook 官方 Dockerfile

  1. # Copyright (c) Jupyter Development Team.
  2. # Distributed under the terms of the Modified BSD License.
  3. ARG BASE_CONTAINER=jupyter/base-notebook
  4. FROM $BASE_CONTAINER
  5. LABEL maintainer="Jupyter Project <jupyter@googlegroups.com>"
  6. USER root
  7. # Install all OS dependencies for fully functional notebook server
  8. RUN apt-get update && apt-get install -yq --no-install-recommends
  9. build-essential
  10. emacs
  11. git
  12. inkscape
  13. jed
  14. libsm6
  15. libxext-dev
  16. libxrender1
  17. lmodern
  18. netcat
  19. pandoc
  20. python-dev
  21. texlive-fonts-extra
  22. texlive-fonts-recommended
  23. texlive-generic-recommended
  24. texlive-latex-base
  25. texlive-latex-extra
  26. texlive-xetex
  27. tzdata
  28. unzip
  29. nano
  30. && rm -rf /var/lib/apt/lists/*
  31. # Switch back to jovyan to avoid accidental container runs as root
  32. USER $NB_UID

例三:nvidia/cuda 的 cuda9.0+cudnn7.6 官方 Dockerfile

  1. ARG IMAGE_NAME
  2. FROM ${IMAGE_NAME}:9.0-devel-ubuntu16.04
  3. LABEL maintainer "NVIDIA CORPORATION <cudatools@nvidia.com>"
  4. ENV CUDNN_VERSION 7.6.3.30
  5. LABEL com.nvidia.cudnn.version="${CUDNN_VERSION}"
  6. RUN apt-get update && apt-get install -y --no-install-recommends
  7. libcudnn7=$CUDNN_VERSION-1+cuda9.0
  8. libcudnn7-dev=$CUDNN_VERSION-1+cuda9.0
  9. &&
  10. apt-mark hold libcudnn7 &&
  11. rm -rf /var/lib/apt/lists/*

Dockerfile 指令解析

  1. # FROM 指定基础的 Docker Image,可以来自官方远程仓库,也可位于本地仓库
  2. # 没有指定镜像标签,则默认使用 latest 标签
  3. FROM ImageName
  4. # 创建镜像的用户
  5. MAINTAINER UserName
  6. MAINTAINER "Jupyter Project <jupyter@googlegroups.com>"
  7. LABEL maintainer="Jupyter Project <jupyter@googlegroups.com>"
  8. # 每条 RUN 指令将在当前镜像基础上执行指定命令,并提交为新的镜像,
  9. # 后续的 RUN 都以之前 RUN 提交的镜像为基础,
  10. # 镜像是分层的,可以通过一个镜像的任何一个历史提交点来创建。
  11. RUN command1 && command2 && ...
  12. # ENV 设置环境变量,后续 RUN 指令使用,并在容器运行时保留
  13. ENV KEY value # 只能设置一个变量
  14. ENV KEY=value # 允许一次设置多个变量
  15. # ARG 定义了一个变量,能让用户可以在构建期间使用 docker build 命令和其参数 --build-arg 对这个变量赋值
  16. # 例三中,可以用如下命令传入参数:
  17. # docker build --build-arg IMAGE_NAME=nvidia/cuda Dockerfile
  18. ARG <name>[=<default value>]
  19. # COPYADD 将宿主目录下 [或者远程文件 URLS] 的文件拷贝进镜像
  20. # ADD 可自动处理 URL 和解压 tar 包,COPY 不能指定远程文件 URLS
  21. COPY src dest
  22. ADD src dest
  23. # WORKDIR 指定在创建 container 后,终端默认登录的 container 的工作目录,未指定则在根目录
  24. WORKDIR
  25. # CMD 指定一个容器启动时要运行的命令
  26. # DockerFile 中可以有多个 CMD 指令,但是只有最后一个生效
  27. # 如果用户启动容器时指定了运行的命令,则会覆盖掉 CMD 指定的命令。
  28. # 如:docker run -it --rm --name tf_11-py_35 -v $PWD:/dl tensorflow/tensorflow:1.11.0-rc2-py3 sh
  29. CMD
  30. CMD echo $HOME
  31. CMD [ "sh", "-c", "echo $HOME" ]
  32. # ENTRYPOINT 配置容器启动后执行的命令,并且不可被 docker run 提供的参数覆盖。
  33. # 每个 Dockerfile 中只能有一个 ENTRYPOINT,当指定多个时,只有最后一个生效。
  34. ENTRYPOINT
  35. # 指定对外的端口号
  36. # 默认为 8888,对于 docker run 指令中的 -p 参数产生影响
  37. EXPOSE
  38. # 容器数据卷,用于数据保存和持久化工作
  39. VOLUME
  40. VOLUME /docker_data

docker build

格式:

  1. docker build [OPTIONS] PATH | URL | -
  2. docker image build [OPTIONS] PATH | URL | -

常用参数:

  1. -f, --file string # Name of the Dockerfile (Default is 'PATH/Dockerfile')
  2. --build-arg list # Set build-time variables
  3. --compress # Compress the build context using gzip
  4. -t, --tag list # Name and optionally a tag in the 'name:tag' format
  5. --rm # Remove intermediate containers after a successful build (default true)

下面通过两个例子展示编写 Dockerfile 及创建镜像的过程。

例一:定制一个简单的 tensorflow 工作环境

python==3.6,numpy==1.14.2,tensorflow==1.12.0

Dockerfile

  1. FROM python:3.6
  2. WORKDIR /tensorflow
  3. COPY requirements.txt ./
  4. RUN pip install --no-cache-dir -r requirements.txt
  5. COPY . .
  6. CMD [ "python", "./tf.py" ] # tf.py 是用户自己的 .py 文件

requirements.txt

  1. numpy==1.14.2
  2. tensorflow==1.12.0

创建镜像

  1. # build image
  2. docker image build -t py36tf:0.0.1 .

创建过程如下,注意 docker 镜像是分层创建的,每次执行命令都会产生新的镜像

  1. Step 1/6 : FROM python:3.6 #下载 python:3.6 的 image
  2. ---> 1c515a624542
  3. Step 2/6 : WORKDIR /home/lizhongding/codeLinux/dp
  4. ---> Running in 4c77d0b86f09 # 设置工作目录,在容器 4c77d0b86f09 中运行
  5. Removing intermediate container 4c77d0b86f09 # 移除中间 container 4c77d0b86f09
  6. ---> 708c4ca1c9dd
  7. Step 3/6 : COPY requirements.txt ./ # 拷贝 requirements.txt,到镜像的根目录下
  8. ---> ee1bde24d6b1
  9. Step 4/6 : RUN pip install --no-cache-dir -r requirements.txt # 安装 numpy==1.14.2,tensorflow==1.12.0 及相关依赖
  10. ---> Running in ed988a6febfe
  11. Removing intermediate container ed988a6febfe
  12. ---> 22348b6a3d9f
  13. Step 5/6 : COPY . . # 将当前目录的文件拷贝进镜像的当前目录
  14. ---> 0117e5a5ae26
  15. Step 6/6 : CMD python tf.py # 执行命令 python tf.py
  16. ---> Running in 4edf63c674f8
  17. Removing intermediate container 4edf63c674f8
  18. ---> 1fde2f3a630f
  19. Successfully built 1fde2f3a630f # 建立镜像 1fde2f3a630f
  20. Successfully tagged py36tf:0.0.1 # REPOSITORY:TAG = py36tf:0.0.1

上传镜像

  1. # 在 hub.docker.com 上注册帐号
  2. docker login
  3. # 建立本地映像与存储库的关联
  4. docker image tag ImageName UserName/repository[:tag] # 默认 tag 为 latest
  5. docker image tag py36tf:0.0.1 lizhongding/py36tf:0.0.1
  6. docker image push lizhongding/py36tf:0.0.1
  7. # 成功后,可在 hub.docker.com 上查看 image
  8. # 相似依赖的镜像,再上传时会尽量利用已有镜像
  9. docker image tag py36tf:0.0.2 lizhongding/py36tf:0.0.2
  10. docker image push lizhongding/py36tf:0.0.2

例二:基于 cuda_9.0,cudnn_7.0 定制可训练对抗生成网络(GAN)的环境

下载最新版的 ananconda3,利用 conda 安装以下 package:

  1. jupyter,Pillow,matplotlib,pyyaml,python-lmdb,scikit-learn,tqdm,
  2. tensorflow-gpu=1.11.0,cudatoolkit=9.0,python=3.6

Dockfile 构建如下。[参考项目:okwrtdsh/anaconda3]

  1. FROM nvidia/cuda:9.0-cudnn7-devel
  2. USER root
  3. ENV DEBIAN_FRONTEND=noninteractive
  4. LANG=C.UTF-8
  5. LC_ALL=C.UTF-8
  6. PATH=/opt/conda/bin:$PATH
  7. NOTEBOOK_DIR=/src/notebooks
  8. NOTEBOOK_IP=0.0.0.0
  9. NOTEBOOK_PORT=8888
  10. RUN apt-get update -qq
  11. && apt-get upgrade -y
  12. && apt-get install --no-install-recommends -y
  13. curl grep sed dpkg wget bzip2 ca-certificates
  14. libglib2.0-0 libxext6 libsm6 libxrender1
  15. git mercurial subversion
  16. libgtk2.0-0
  17. # 最新版 annaconda3 安装
  18. && echo 'export PATH=/opt/conda/bin:$PATH' > /etc/profile.d/conda.sh
  19. && ANACONDA_INSTALL_SCRIPT='Anaconda3-2019.03-Linux-x86_64.sh'
  20. && wget --quiet https://repo.continuum.io/archive/$ANACONDA_INSTALL_SCRIPT -O ~/anaconda.sh
  21. && /bin/bash ~/anaconda.sh -b -p /opt/conda
  22. && rm ~/anaconda.sh
  23. && apt-get clean
  24. && rm -rf /var/lib/apt/lists/*
  25. RUN conda config --add channels conda-forge
  26. && conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
  27. && conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
  28. && conda install -y --quiet
  29. jupyter
  30. Pillow
  31. matplotlib
  32. pyyaml
  33. python-lmdb
  34. scikit-learn
  35. tqdm
  36. tensorflow-gpu=1.11.0
  37. cudatoolkit=9.0
  38. python=3.6
  39. && conda install -c menpo opencv3
  40. && conda clean -tipsy
  41. # 安装 tini
  42. ENV TINI_VERSION=v0.18.0
  43. ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /usr/bin/tini
  44. RUN chmod +x /usr/bin/tini
  45. ENTRYPOINT [ "/usr/bin/tini", "--" ]
  46. CMD jupyter notebook
  47. --notebook-dir=${NOTEBOOK_DIR}
  48. --ip=${NOTEBOOK_IP}
  49. --port=${NOTEBOOK_PORT}
  50. --NotebookApp.token=''
  51. --no-browser
  52. --allow-root

上传镜像

  1. docker image build -t :0.0.1 .
  2. docker login
  3. docker image tag gan:0.0.1 lizhongding/gan:0.0.1
  4. docker image push lizhongding/gan:0.0.1

docker save and docker load

docker 镜像可以不必在 docker hub 存取,也可本地存取。

格式:docker save [OPTIONS] IMAGE [IMAGE...]

参数:--output , -o

示例:

  1. docker save busybox > busybox.tar
  2. ls -sh busybox.tar
  3. # 2.7M busybox.tar
  4. docker save --output busybox.tar busybox
  5. ls -sh busybox.tar
  6. # 2.7M busybox.tar
  7. # 用 gzip 压缩
  8. docker save gan:0.0.1 | gzip > gan:0.0.1.tar.gz

格式:docker load [OPTIONS]

参数:--input , -i

  1. docker load < busybox.tar.gz
  2. docker load --input fedora.tar
  3. docker images

nvidia-docker

当镜像中包含 gpu 的操作时,需要 nvidia-docker 来 run 镜像。

ebe6890f5254ee93ba4032a0041edba7.png
  1. # 卸载 nvidia-docker 1.0
  2. docker volume ls -q -f driver=nvidia-docker | xargs -r -I{} -n1 docker ps -q -a -f volume={} | xargs -r docker rm -f
  3. sudo apt-get purge nvidia-docker
  4. # 安装依赖
  5. distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
  6. curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
  7. curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
  8. sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
  9. sudo systemctl restart docker
  10. # 用 nvidia-smi 测试镜像
  11. docker run --gpus all nvidia/cuda:9.0-base nvidia-smi
  12. # 安装 nvidia-docker2
  13. sudo apt-get install nvidia-docker2
  14. sudo pkill -SIGHUP dockerd
  15. # 用 tensorflow-gpu 进行测试
  16. # 注意 tensorflow/tensorflow 镜像中支持的 cuda 版本要与本机 cuda 版本匹配
  17. # 最新版要求 cuda>=10.0
  18. nvidia-docker run -it --rm --name tf_gpu tensorflow/tensorflow:1.11.0-devel-gpu-py3
  19. python -c "import tensorflow as tf; tf.enable_eager_execution(); print(tf.reduce_sum(tf.random_normal([1000, 1000])))"

docker exec

在正在运行的容器中运行命令

格式:docker exec [OPTIONS] CONTAINER COMMAND [ARG...]

参数:

  1. --detach, -d # 分离模式:在后台运行命令
  2. --detach-keys # 覆盖用于分离容器的键序列
  3. --env, -e # 设置环境变量
  4. --interactive, -i # 即使没有连接,也要保持STDIN打开
  5. --privileged # 为命令提供扩展权限
  6. --tty, -t # 分配伪TTY
  7. --user, -u # 用户名或UID(格式:<name | uid> [:<group | gid>])
  8. --workdir, -w # 容器内的工作目录

示例:

  1. docker restart tf_gpu
  2. # 访问 container
  3. docker exec -it tf_gpu bash
  4. docker exec -it tf_gpu python

docker inspect

格式:docker inspect [OPTIONS] NAME|ID [NAME|ID...]

  1. docker inspect tf_gpu
  2. docker inspect 157efda75cac
  3. # 打印 json 字符串,所有相关信息都可以查看

docker attach

将本地标准输入,输出和错误流附加到正在运行的容器。

简单说,就是可以在命令行进入并操作容器。

格式:docker attach [OPTIONS] CONTAINER

  1. docker attach tf_gpu
  2. docker attach f8268dbfd125

Namespace

namespace 是实现隔离的机制。

  1. 每个 namespace 中的进程只能影响同一个 namespace 或子 namespace 中的进程。
  2. /proc 包含正在运行的进程,在 container 中的 /proc 目录只能看到自己 namespace 中的进程。
  3. namespace 允许嵌套,父 namespace 可以影响子 namespace 的进程,子 namespace 可在父 namespace 中看到,但是具有不同的 pid。
  4. 网络隔离是通过网络 namespace 实现的, 每个网络 namespace 有独立的 network devices,IP addresses,IP routing tables,/proc/net 目录。这样每个 container 的网络就能隔离开来。 docker 默认采用 veth 的方式将 container 中的虚拟网卡同 host 上的一个 docker bridge 连接在一起。
  5. 每个 container 有不同的 user 和 group id, container 用户在 container 内部执行程序而非 Host 上的用户。

参考链接

https://blog.csdn.net/zmx729618/article/details/72930474​blog.csdn.net | Docker Documentation​docs.docker.com
3f8a8e40493e74a7ad6e8256e2ee4f52.png
Docker Hub​hub.docker.com A Docker Tutorial for Beginners​docker-curriculum.com
6a2d9028b5507e45914598df2d613e12.png
okwrtdsh/anaconda3​github.com
76a16678ab111532cf02fac701a7def0.png
DockerFile解析​www.jianshu.com
a1ce58807f35deda4b9fcdaeb4302665.png
Docker容器技术之Docker file​mp.weixin.qq.com
f50244052bafa354d576657ed38362e8.png
https://www.centos.bz/2016/12/dockerfile-arg-instruction/​www.centos.bz CMD 容器启动命令 · Docker -- 从入门到实践​yeasy.gitbooks.io docker load​docs.docker.com
3f8a8e40493e74a7ad6e8256e2ee4f52.png
NVIDIA/nvidia-docker​github.com
823fba22fe9df410cd6f015f0ea88c35.png
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/159241
推荐阅读
相关标签
  

闽ICP备14008679号