avatar

Docker容器

Docker容器

Docker 容器介绍

docker 是什么

Docker 是Docker.Inc 公司开源的一个基于LXC 技术之上构建的Container 容器引擎, 源代码托管
在GitHub 上, 基于Go 语言并遵从Apache2.0 协议开源。
Docker 是通过内核虚拟化技术(namespaces 及cgroups 等)来提供容器的资源隔离与安全保障等。由于Docker 通过操作系统层的虚拟化实现隔离,所以Docker 容器在运行时,不需要类似虚拟机(VM)额外的操作系统开销,提高资源利用率。

namespace 命名空间

1
2
3
4
5
6
Mount Namespace - 用于隔离文件系统的挂载点
UTS Namespace - 用于隔离HostName 和DomianName
IPC Namespace - 用于隔离进程间通信
PID Namespace - 用于隔离进程ID
Network Namespace - 用于隔离网络
User Namespace - 用于隔离用户和用户组UID/GID

cgroups资源限制

控制组(cgroups)是Linux 内核的一个特性,主要用来对共享资源进行隔离、限制、审计等。只有
能控制分配到容器的资源,才能避免当多个容器同时运行时的对系统资源的竞争。
控制组技术最早是由Google 的程序员在2006 年提出,Linux 内核自2.6.24 开始支持。
控制组可以提供对容器的内存、CPU、磁盘IO 等资源的限制和审计管理。

容器与虚拟机对比

image

image

docker 的三个重要概念

Image(镜像):
那么镜像到底是什么呢?Docker 镜像可以看作是一个特殊的文件系统,除了提供容器运行时所需的
程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。

Container(容器)
容器(Container)的定义和镜像(Image)几乎一模一样,也是一堆层的统一视角,唯一区别在于容器的最上面那一层是可读可写的。

Repository(仓库)
镜像仓库是Docker 用来集中存放镜像文件的地方,类似于我们之前常用的代码仓库。
通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。
我们可以通过<仓库名>:<标签>的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将
以Latest 作为默认标签。

image

docker 的组成部分

docker 是传统的CS 架构分为docker client 和docker server
Docker 客户端是Docker 用户与Docker 交互的主要方式。当您使用Docker 命令行运行命令时,
Docker 客户端将这些命令发送给服务器端,服务端将执行这些命令。
Docker 命令使用Docker API 。Docker 客户端可以与多个服务端进行通信。

image

Docker 安装部署

国内源安装docker-ce

这里我们使用清华源:

1
https://mirrors.tuna.tsinghua.edu.cn/help/docker-ce/

操作步骤:

1
2
3
4
5
6
7
yum remove docker docker-common docker-selinux docker-engine
yum install -y yum-utils device-mapper-persistent-data lvm2
wget -O /etc/yum.repos.d/docker-ce.repo https://download.docker.com/linux/centos/docker-ce.repo
sed -i 's+download.docker.com+mirrors.tuna.tsinghua.edu.cn/docker-ce+' /etc/yum.repos.d/docker-ce.repo
yum makecache fast
yum install docker-ce
systemctl start docker

国内远镜像加速配置

1
2
3
4
5
6
7
8
mkdir -p /etc/docker
tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://1660dchq.mirror.aliyuncs.com"]
}
EOF
systemctl daemon-reload
systemctl restart docker

快速安装

1
2
3
4
5
思路:
1.开启yum 缓存
2.全新环境安装docker
3.将缓存的包全部打包出来
4.安装的时候使用rpm -ivh *.rpm 方式安装

运行第一个容器

运行一个Hello world

1
docker run alpine /bin/echo "Hello world"

Docker 镜像和容器管理

镜像相关命令

搜索镜像

选择镜像建议:

1.优先选择官方的

2.选择星星多的

1
docker search centos

3.搜索镜像并查看该镜像的tag

默认我们使用命令查看是看不到镜像的版本的,可以通过docker-hub 查看,但是国内原因。。。
这里可以使用仓库的restful APIl 来查询
jq 是一个json 格式化工具,需要提前安装

1
2
yum install jq -y
curl -s https://registry.hub.docker.com/v1/repositories/centos/tags|jq

获取镜像

1
docker pull centos:7

查看镜像

1
docker images

删除镜像

1
docker rmi centos:7

导出镜像

1
2
docker save centos:7 -o centos-v7.tar
docker save centos:7 > centos-v7.tar

导入镜像

1
2
docker load -i centos-v7.tar
docker load < centos-v7.tar

容器相关命令

启动容器

1
2
3
4
1.启动一个容器并保持不退出
docker run -it -d centos:7 /bin/bash
2.启动一个容器并保持不退出并自定义命名
docker run --name mycentos -it -d centos:7 /bin/bash

停止容器

1
docker stop 容器ID

查看容器的详细信息

1
docker inspect 容器名或ID

查看正在运行和已经停止的容器

1
2
3
4
docker ps
docker ps -a
docker ps -q
docker ps -qa

进入容器

docker exec 会分配一个新的终端tty

1
2
3
4
1.docker exec 会分配一个新的终端tty
docker exec -it 容器ID /bin/bash
2.docker attach 会使用同一个终端
docker attach 容器ID

删除容器

删除单个容器

1
docker rm 容器ID

批量删除容器

1
2
docker stop $(docker ps -q)
docker rm $(docker ps -aq)

Docker 网络管理

随机映射端口

1
docker run -P -d nginx:latest

指定映射端口

1
docker run -p 8080:80 -d nginx:latest

如果想多个容器使用8080 端口,可以通过添加多个IP 地址实现

1
2
3
ifconfig eth0:1 10.0.1.52 up
docker run -d -p 10.0.1.51:8080:80 nginx:latest
docker run -d -p 10.0.1.52:8080:80 nginx:latest

进入容器里修改站点目录,然后访问测试

1
2
3
4
docker exec -it bdb2a4e7e24d /bin/bash
echo "web01" > /usr/share/nginx/html/index.html
docker exec -it 31c1de138dda /bin/bash
echo "web02" > /usr/share/nginx/html/index.html

访问测试:

1
[root@docker01 ~]# curl 10.0.1.51:8080

跨主机通信

Docker 数据卷管理

持久化数据卷

1
-v 宿主机目录:容器内目录

下面我们来创建一个html5 小游戏

创建游戏代码目录

1
2
3
mkdir /data/ -p
cd /data/
unzip xiaoniaofeifei.zip -d xiaoniao

创建容器并映射数据卷

1
2
3
4
docker stop $(docker ps -q)
docker rm $(docker ps -aq)
docker run -d -p 80:80 -v /data/xiaoniao:/usr/share/nginx/html nginx:latest
docker ps

访问游戏页面

1
10.0.1.51

持久化数据卷容器

1
--volumes-from (跟某一个已经存在的容器挂载相同的卷)

查看当前容器有哪些卷

1
docker volume ls

下面我们利用容器卷创建一个小游戏

创建一个容器卷

1
2
docker run -d -p 8080:80 --volume xiaoniao:/usr/share/nginx/html nginx:latest
docker volume ls

查看卷的目录

1
docker volume inspect xiaoniao

复制代码目录到容器卷目录

1
\cp -a /data/xiaoniao/* /var/lib/docker/volumes/xiaoniao/_data/

使用容器卷创建新容器

1
docker run -d -p 8090:80 --volume xiaoniao:/usr/share/nginx/html nginx:latest

实验-访问不同端口展示不同页面

需求:
访问8080 端口,展示xiaoniao 首页
访问8090 端口,展示游戏集合页面

准备nginx 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cat >/data/game.conf<<EOF
server {
listen 8080;
server_name localhost;
location / {
root /opt/game/;
index index.html index.htm;
}
}

server {
listen 8090;
server_name localhost;
location / {
root /opt/xiaoniao/;
index index.html index.htm;
}
}
EOF

上传代码目录

1
2
3
4
5
6
7
[root@docker01 /data]# ll
总用量18896
drwxr-xr-x 5 root root 73 9 月7 23:03 game
-rw-r--r-- 1 root root 309 9 月7 22:57 game.conf
-rw-r--r-- 1 root root 19248295 8 月28 09:48 html5.zip
drwxr-xr-x 3 root root 92 9 月7 22:15 xiaoniao
-rw-r--r-- 1 root root 91014 9 月7 22:11 xiaoniaofeifei.zip

创建容器并挂载

需要挂载的内容:
1.nginx 配置文件
2.游戏目录
创建容器命令

1
2
3
4
docker stop $(docker ps -q)
docker rm $(docker ps -aq)
docker run -d -p 8080:8080 -p 8090:8090 -v /data/xiaoniao:/opt/xiaoniao -v /data/game:/opt/game -v /data/game.conf:/etc/nginx/conf.d/game.conf nginx:latest
docker ps

Docker 镜像手动构建

手动制作游戏镜像

下面我们基于centos 容器制作一个新镜像,并安装nginx 服务

启动一个容器并安装nginx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
docker run -it centos:7 /bin/bash
yum install wget install openssh-clients -y
rm -rf /etc/yum.repos.d/*
wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
sed -i -e '/mirrors.cloud.aliyuncs.com/d' -e '/mirrors.aliyuncs.com/d' /etc/yum.repos.d/CentOS-Base.repo
cat >/etc/yum.repos.d/nginx.repo<<EOF
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/\$releasever/\$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/\$releasever/\$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
EOF
yum makecache fast
yum install nginx -y

上传代码目录并配置nginx 配置文件

1
2
scp -r 10.0.1.51:/data/* /opt/
mv /opt/game.conf /etc/nginx/conf.d/

将容器提交为新的镜像

1
2
3
docker ps
docker commit 容器ID game:v1
docker images

测试镜像功能是否可用

1
docker run -p 8080:8080 -p 8090:8090 -d game:v1 nginx -g 'daemon off;'

将新镜像导出

1
2
docker save game:v1 > game-v1.tar
docker load < game-v1.tar

手动制作云盘镜像

创建容器

1
docker run -d -p 80:80 --name clould game:v1 nginx -g 'daemon off;'

进入容器安装php 并求改运行用户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
yum install wget install openssh-clients unzip -y
rm -rf /etc/yum.repos.d/*
wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
sed -i -e '/mirrors.cloud.aliyuncs.com/d' -e '/mirrors.aliyuncs.com/d' /etc/yum.repos.d/CentOS-Base.repo
cat >/etc/yum.repos.d/nginx.repo<<EOF
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/\$releasever/\$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/\$releasever/\$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
EOF
yum makecache fast
yum install nginx php-fpm php-mbstring php-gd -y
sed -i '/^user/c user = nginx' /etc/php-fpm.d/www.conf
sed -i '/^group/c group = nginx' /etc/php-fpm.d/www.conf
sed -i '/daemonize/s#no#yes#g' /etc/php-fpm.conf
php-fpm -c /etc/php.ini -y /etc/php-fpm.conf
ps -ef|grep php

配置nginx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cat> /etc/nginx/conf.d/cloud.conf <<EOF  
server {
listen 80;
server_name localhost;
root /code/kod;
index index.php index.html;

location ~ \.php\$ {
root /code/kod;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
include fastcgi_params;
}
}
EOF
rm -rf /etc/nginx/conf.d/default.conf
nginx -t
nginx -s reload

下载代码目录

1
2
3
4
mkdir /code
scp 10.0.1.51:/data/kodexplorer4.40.zip
unzip kodexplorer4.40.zip -d kod
chown -R nginx:nginx /code/

容器启动脚本命令

1
2
3
4
5
6
cat >init.sh<<EOF
#!/bin/bash
php-fpm -c /etc/php.ini -y /etc/php-fpm.conf
nginx -g 'daemon off;'
EOF
chmod +x init.sh

提交新的镜像

1
docker commit 9e691858a4bd kod:v1

启动测试

1
2
3
4
docker stop $(docker ps -q)
docker rm $(docker ps -aq)
docker run -p 80:80 -v /data/kod/data:/code/kod/data/ -d kod:v1 /bin/bash /root/init.sh
docker ps

访问测试

image

Dockerfile 自动构建Docker 镜像

Dockerfile 操作命令说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
Docker 通过对于在Dockerfile 中的一系列指令的顺序解析实现自动的image 的构建
通过使用build 命令,根据Dockerfiel 的描述来构建镜像
通过源代码路径的方式
通过标准输入流的方式
Dockerfile 指令:
只支持Docker 自己定义的一套指令,不支持自定义
大小写不敏感,但是建议全部使用大写
根据Dockerfile 的内容顺序执行
FROM:
FROM {base 镜像}
必须放在DOckerfile 的第一行,表示从哪个baseimage 开始构建
MAINTAINER:
可选的,用来标识image 作者的地方
RUN:
每一个RUN 指令都会是在一个新的container 里面运行,并提交为一个image 作为下一个RUN 的base
一个Dockerfile 中可以包含多个RUN,按定义顺序执行
RUN 支持两种运行方式:
RUN <cmd> 这个会当作/bin/sh -c “cmd” 运行
RUN [“executable”,“arg1”,。。],Docker 把他当作json 的顺序来解析,因此必须使用双引号,
而且executable 需要是完整路径
RUN 都是启动一个容器、执行命令、然后提交存储层文件变更。第一层RUN command1 的执行仅仅是当前进程,
一个内存上的变化而已,其结果不会造成任何文件。而到第二层的时候,启动的是一个全新的容器,跟第一层的容
器更完全没关系,自然不可能继承前一层构建过程中的内存变化。而如果需要将两条命令或者多条命令联合起来执
行需要加上&&。如:cd /usr/local/src && wget xxxxxxx
CMD:
CMD 的作用是作为执行container 时候的默认行为(容器默认的启动命令)
当运行container 的时候声明了command,则不再用image 中的CMD 默认所定义的命令
一个Dockerfile 中只能有一个有效的CMD,当定义多个CMD 的时候,只有最后一个才会起作用
CMD 定义的三种方式:
CMD <cmd> 这个会当作/bin/sh -c "cmd"来执行
CMD ["executable","arg1",....]
CMD ["arg1","arg2"],这个时候CMD 作为ENTRYPOINT 的参数
EXPOSE 声明端口
格式为EXPOSE <端口1> [<端口2>...]。
EXPOSE 指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启
这个端口的服务。在Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护
端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是docker run -P 时,会自动随机
映射EXPOSE 的端口。
entrypoint:
entrypoint 的作用是,把整个container 变成了一个可执行的文件,这样不能够通过替换CMD 的方法来改变创
建container 的方式。但是可以通过参数传递的方法影响到container 内部
每个Dockerfile 只能够包含一个entrypoint,多个entrypoint 只有最后一个有效
当定义了entrypoint 以后,CMD 只能够作为参数进行传递
entrypoint 定义方式:
entrypoint ["executable","arg1","arg2"],这种定义方式下,CMD 可以通过json 的方式来定义entrypoint
的参数,可以通过在运行container 的时候通过指定command 的方式传递参数
entrypoint <cmd>,当作/bin/bash -c "cmd"运行命令
ADD & COPY:
当在源代码构建的方式下,可以通过ADD 和COPY 的方式,把host 上的文件或者目录复制到image 中
ADD 和COPY 的源必须在context 路径下
当src 为网络URL 的情况下,ADD 指令可以把它下载到dest 的指定位置,这个在任何build 的方式下都可以
work
ADD 相对COPY 还有一个多的功能,能够进行自动解压压缩包
ENV:
ENV key value
用来设置环境变量,后续的RUN 可以使用它所创建的环境变量
当创建基于该镜像的container 的时候,会自动拥有设置的环境变量
WORKDIR:
用来指定当前工作目录(或者称为当前目录)
当使用相对目录的情况下,采用上一个WORKDIR 指定的目录作为基准
USER:
指定UID 或者username,来决定运行RUN 指令的用户
ONBUILD:
ONBUILD 作为一个trigger 的标记,可以用来trigger 任何Dockerfile 中的指令
可以定义多个ONBUILD 指令
当下一个镜像B 使用镜像A 作为base 的时候,在FROM A 指令前,会先按照顺序执行在构建A 时候定义的ONBUILD指令
ONBUILD <DOCKERFILE 指令> <content>
VOLUME:
用来创建一个在image 之外的mount point,用来在多个container 之间实现数据共享
运行使用json array 的方式定义多个volume
VOLUME ["/var/data1","/var/data2"]
或者plain text 的情况下定义多个VOLUME 指令

使用Dockerfile 创建基础系统镜像

使用Dockerfile 创建云盘

准备需要的文件

1
2
[root@docker01 ~/dockerfile/kod]# ls
CentOS-Base.repo cloud.conf dockerfile epel.repo init.sh kodexplorer4.40.zip nginx.repo

编写dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@docker01 ~/dockerfile/kod]# cat dockerfile
FROM centos:latest
ADD CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo
ADD epel.repo /etc/yum.repos.d/epel.repo
ADD nginx.repo /etc/yum.repos.d/nginx.repo
RUN yum install nginx php-fpm unzip php-gd php-mbstring -y
RUN rm -rf /etc/nginx/conf.d/default.conf
RUN sed -i '/^user/c user = nginx' /etc/php-fpm.d/www.conf
RUN sed -i '/^group/c group = nginx' /etc/php-fpm.d/www.conf
RUN sed -i '/daemonize/s#no#yes#g' /etc/php-fpm.conf
ADD cloud.conf /etc/nginx/conf.d/cloud.conf
RUN mkdir /code
WORKDIR /code
ADD kodexplorer4.40.zip /code/kodexplorer4.40.zip
RUN unzip kodexplorer4.40.zip
RUN chown -R nginx:nginx .
VOLUME /code
ADD init.sh /init.sh
EXPOSE 80
CMD ["/bin/bash","/init.sh"]

构建新镜像

1
2
3
4
[root@docker01 ~/dockerfile/kod]# docker build --network=host -t centos_kod:v1 .
[root@docker01 ~/dockerfile/kod]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centos_kod v1 87071ea7c86b 2 minutes ago 465MB

基于新镜像运行容器

1
2
[root@docker01 ~/dockerfile/kod]# docker run -d -p 80:80 centos_kod:v1
4d1007d7194d434248762e5aca76070206643f0f0f533f2112d0359b25fe7ba0

Docker 私有仓库

企业级的docker-harbor

部署思路

1
2
3
4
5
第一步:安装docker 和docker-compose
第二步:下载harbor-offline-installer-v1.9.0-rc1.tgz
第三步:上传到/opt,并解压
第四步:修改harbor.yml 配置文件hostname = 10.0.0.11 harbor_admin_password = 123456
第五步:执行install.sh

安装

1.下载软件包

1
2
3
cd /opt
wget https://github.com/goharbor/harbor/releases/download/v1.9.3/harbor-offline-installer-v1.9.3.tgz
tar xf harbor-offline-installer-v1.9.0-rc1.tgz

2.修改配置

1
2
3
vim harbor.yml 
hostname: 10.0.1.5
harbor_admin_password: 123456

3.下载docker-compose

1
yum install docker-compose -y

4.安装harbor

1
./install.sh

5.修改镜像名称

1
docker tag 019a54813db0 10.0.1.51/linux/centos_kod:v1

6.修改docker配置添加信任仓库

1
2
3
4
5
6
7
8
cat >/etc/docker/daemon.json<<EOF 
{
"registry-mirrors": ["https://1660dchq.mirror.aliyuncs.com"],
"insecure-registries": ["http://10.0.1.51"]
}
EOF
systemctl daemon-reload
systemctl restart docker

7.docker登录到harbor

1
docker login 10.0.1.51

8.上传镜像到私有仓库

1
docker push 10.0.1.51/linux/centos_kod:v1

9.客户端使用仓库下载镜像

1
docker run -p 8080:80 -d 10.0.1.51/linux/centos_kod:v1

10.访问测试

image

docker运行zabbix

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
docker run --name mysql-server -t \
-e MYSQL_DATABASE="zabbix" \
-e MYSQL_USER="zabbix" \
-e MYSQL_PASSWORD="zabbix_pwd" \
-e MYSQL_ROOT_PASSWORD="root_pwd" \
-d mysql:5.7 \
--character-set-server=utf8 --collation-server=utf8_bin

docker run --name zabbix-server-mysql -t \
-e DB_SERVER_HOST="mysql-server" \
-e MYSQL_DATABASE="zabbix" \
-e MYSQL_USER="zabbix" \
-e MYSQL_PASSWORD="zabbix_pwd" \
-e MYSQL_ROOT_PASSWORD="root_pwd" \
--link mysql-server:mysql \
-p 10051:10051 \
-d zabbix/zabbix-server-mysql:latest

docker run --name zabbix-web-nginx-mysql -t \
-e DB_SERVER_HOST="mysql-server" \
-e MYSQL_DATABASE="zabbix" \
-e MYSQL_USER="zabbix" \
-e MYSQL_PASSWORD="zabbix_pwd" \
-e MYSQL_ROOT_PASSWORD="root_pwd" \
--link mysql-server:mysql \
--link zabbix-server-mysql:zabbix-server \
-p 80:80 \
-d zabbix/zabbix-web-nginx-mysql:latest

docker运行gitlab

1
2
3
4
5
6
7
8
9
docker run --detach \
--hostname 10.0.1.51 \
--publish 443:443 --publish 80:80 --publish 22:22 \
--name gitlab \
--restart always \
--volume /srv/gitlab/config:/etc/gitlab \
--volume /srv/gitlab/logs:/var/log/gitlab \
--volume /srv/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce:latest
文章作者: Wu Fei
文章链接: http://linuxwf.com/2020/04/09/Docker%E5%AE%B9%E5%99%A8/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 WF's Blog
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论