1,Docker是一个,快速构建、运行、管理应用的工具 。
2,前面我们了解过在Linux操作系统的常见的命令以及如何在Linux中部署一个人单体的项目。感受如何呢???
命令太多了,记不住
软件安装包名字复杂,不知道去哪里找
安装和部署步骤复杂,容易出错
3,
这就是我今天要为大家带来的Docker技术。你会发现,有了Docker以后项目的部署如丝般顺滑,大大减少了运维工作量。
即便对Linux不熟悉,你也能轻松的部署各种常见的软件,java项目等。
一,Docker快速入门:
1.1,部署mysql:
我们传统的部署MySQL的方式大概步骤有:
-
搜索并下载MySQL安装包
-
上传至Linux环境
-
编译和配置环境
-
安装
使用Docker安装,仅仅需要一步即可。
如果我们用mysql的镜像就可以直接使用,但是我们要是没有,也是可以执行这个命令的,会先下载这个镜像,然后在创建这个容器。
docker run -d \
--name mysql \
-p 3306:3306 \
-e TZ=Asia/Shanghai \
-e MYSQL_ROOT_PASSWORD=123 \
mysql
命令:文章来源地址https://www.toymoban.com/news/detail-849063.html
[root@localhost /]# docker run -d \
> --name mysql \
> -p 3306:3306 \
> -e TZ=Asia/Shanghai \
> -e MYSQL_ROOT_PASSWORD=123 \
> mysql
由于网速的原因,我已经停止了下载,我们可以查看一个我们没有下载成功有没有这个镜像。
接下来,我上传自己本地的mysql压缩包。
我进到了我的 /root目录下,然后把我们自己电脑里面的mysql.tar包,上传到/root目录里面
上传成功了
进到这个目录里面查看:
命令:
[root@localhost /]# cd /root
[root@localhost ~]# ll
由于下载的mysql.tar还仅仅只是一个压缩包,还不是我们的Docker镜像,所以我们现在因该把这个压缩包,变成镜像。
命令:
[root@localhost ~]# docker load -i mysql.tar
现在我们再来查看,已经有一个叫mysql的镜像了。
命令:
[root@localhost ~]# docker images
我们在执行我们前面的创建MySQL的容器的命令看一下效果:
命令:
[root@localhost ~]# docker run -d \
> --name mysql \
> -p 3306:3306 \
> -e TZ=Asia/Shanghai \
> -e MYSQL_ROOT_PASSWORD=123 \
> mysql
32aeee67c725834f8fef066b8d0e03499fbb2a1c1a318add048ea41078d0f4a2
[root@localhost ~]#
我们用数据库的客户端连接工具,来测试一下:
可以看到我们已经连接成功了
分析:
1,我们可以发现,当我们执行命令后,Docker做的第一件事情,是去自动搜索并下载了MySQL,然后会自动运行MySQL,我们完全不用插手,是不是非常方便。
2,Docker会自动搜索并下载MySQL。注意:这里下载的不是安装包,而是**镜像。**镜像中不仅包含了MySQL本身,还包含了其运行所需要的环境、配置、系统级函数库。
3,因此它在运行时就有自己独立的环境,就可以跨系统运行,也不需要手动再次配置环境了。这套独立运行的隔离环境我们称为容器。
说明:
镜像: 英文名是 image
容器:英文名是 container
Docker会根据命令中的镜像名称自动搜索并下载镜像,那么问题来了,它是去哪里搜索和下载镜像的呢?这些镜像又是谁制作的呢?
Docker镜像交流社区:
https://hub.docker.com/
总之:镜像的来源有两种。
基于官方基础镜像自己制作
直接去DockerRegistry下载
1-2,命令解读:
虽然我们Docker,的安装MySQL非常方便,我们执行的命令到底是什么意思呢?
docker run -d :创建并运行一个容器, -d 则是让容器以后台进程运行。
--name mysql :: 给容器起个名字叫`mysql`,我们可以随便起。
`-p 3306:3306` : 设置端口映射
1,容器是隔离环境,外界不可访问。但是可以将宿主机端口映射容器内到端口,当访问宿主机指定端口时,就是在访问容器内的端口了。
容器内端口往往是由容器内的进程决定,例如MySQL进程默认端口是3306,因此容器内端口一定是3306;而宿主机端口则可以任意指定,一般与容器内保持一致。
格式:
-p 宿主机端口:容器内端口
,示例中就是将宿主机的3306映射到容器内的3306端口2,e TZ=Asia/Shanghai` : 配置容器内进程运行时的一些参数
- 格式:`-e KEY=VALUE`,KEY和VALUE都由容器内进程决定
- 案例中,`TZ=Asia/Shanghai`是设置时区;`MYSQL_ROOT_PASSWORD=123`是设置MySQL默认密码
3,mysql : 设置镜像名称,Docker会根据这个名字搜索并下载镜像
- 格式:`REPOSITORY:TAG`,例如`mysql:8.0`,其中`REPOSITORY`可以理解为镜像名,`TAG`是版本号
- 在未指定`TAG`的情况下,默认是最新版本,也就是`mysql:latest`
二,Docker基础:
2.1,常见命令:
我们可以参考官方文档:
Use the Docker command line | Docker Docs
2.1.1命令介绍:
命令 | 说明 | 文档地址 |
---|---|---|
docker pull | 拉取镜像 | docker pull |
docker push | 推送镜像到DockerRegistry | docker push |
docker images | 查看本地镜像 | docker images |
docker rmi | 删除本地镜像 | docker rmi |
docker run | 创建并运行容器(不能重复创建) | docker run |
docker stop | 停止指定容器 | docker stop |
docker start | 启动指定容器 | docker start |
docker restart | 重新启动容器 | docker restart |
docker rm | 删除指定容器 | docs.docker.com |
docker ps | 查看容器 | docker ps |
docker logs | 查看容器运行日志 | docker logs |
docker exec | 进入容器 | docker exec |
docker save | 保存镜像到本地压缩文件 | docker save |
docker load | 加载本地压缩文件到镜像 | docker load |
docker inspect | 查看容器详细信息 | docker inspect |
常用命令操作:
拉取镜像:
docker pull 镜像名
命令:
[root@localhost ~]# docker pull nginx
查看本地镜像:
docker images
命令:
[root@localhost ~]# docker images
删除镜像:
docker rmi 镜像名
命令:
[root@localhost ~]# docker rmi nginx
创建并运行容器:
docker run
命令:
[root@localhost ~]# docker run -d --name nginx -p 80:80 nginx
查看效果:
停止指定容器:
docker stop 容器名
命令:
[root@localhost ~]# docker stop nginx
查看效果:
启动指定容器:
docker start 容器名
命令:
[root@localhost ~]# docker start nginx
重新启动指定容器:
docker restart
命令:
[root@localhost ~]# docker restart nginx
删除指定容器:
docker rm [-f] 容器名
命令:
[root@localhost ~]# docker rm nginx
Error response from daemon: cannot remove container "/nginx": container is running: stop the container before removing or force remove
[root@localhost ~]# docker rm -f nginx
查看容器:
docker ps
命令:
[root@localhost ~]# docker ps
查看容器运行日志:
docker logs 容器名
命令:
[root@localhost ~]# docker logs nginx
进入容器:
命令:
[root@localhost ~]# docker exec -it nginx bash
[root@localhost ~]# docker exec -it mysql mysql -uroot -p123
保存镜像到本地压缩文件:
docker save -o [保存的目标文件名称] [镜像名称]
命令:
[root@localhost ~]# docker save -o mynginx.tar nginx
[root@localhost ~]#
加载本地压缩文件到镜像:
docker load -i
命令:
[root@localhost ~]# docker load -i mynginx.tar
看效果:
查看容器详细信息:
docker inspect 容器名
命令:
[root@localhost ~]# docker inspect nginx
2.1.2,命令别名:
我们可以给常用的Docker命令起别名,方便我们访问。
#修改 /root/.bashrc 文件
vi /root/.bashrc
#把下面的内容填写到这个配置文件里面
alias dps='docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"'
alias dis='docker images'
然后,执行命令使别名生效:
source /root/.bashrc
接下来我们就试一下新的命令吧
命令:
[root@localhost ~]# vi /root/.bashrc
配置:
alias dps='docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"'
alias dis='docker images'
使配置生效
命令:
source /root/.bashrc
测试效果:
2.2数据卷:
2.2.1什么是数据卷?
数据卷(volume)是一个虚拟目录,是容器内目录与宿主机目录之间映射的桥梁。
以Nginx为例,我们知道Nginx中有两个关键的目录:
- html:放置一些静态资源
- conf:放置配置文件
如果我们要让Nginx代理我们的静态资源,最好是放到`html`目录;如果我们要修改Nginx的配置,最好是找到`conf`下的`nginx.conf`文件。
但遗憾的是,容器运行的Nginx所有的文件都在容器内部。所以我们必须利用数据卷将两个目录与宿主机目录关联,方便我们操作。
在上图中:
我们创建了两个数据卷:
conf
、html
Nginx容器内部的
conf
目录和html
目录分别与两个数据卷关联。而数据卷
conf
和html
分别指向了宿主机的/var/lib/docker/volumes/conf/_data
目录和/var/lib/docker/volumes/html/_data
目录
这样以来,容器内的conf和html目录就 与宿主机的`conf`和`html`目录关联起来,我们称为挂载。此时,我们操作宿主机的/var/lib/docker/volumes/html/_data就是在操作容器内的/usr/share/nginx/html/_data目录。只要我们将静态资源放入宿主机对应目录,就可以被Nginx代理了。
小提示:
/var/lib/docker/volumes这个目录就是默认的存放所有容器数据卷的目录,其下再根据数据卷名称创建新目录,格式为/数据卷名/_data。
2.2.2 数据卷命令:
常见的数据卷相关命令有:
命令 | 说明 | 文档地址 |
---|---|---|
docker volume create | 创建数据卷 | docker volume create |
docker volume ls | 查看所有数据卷 | docs.docker.com |
docker volume rm | 删除指定数据卷 | docs.docker.com |
docker volume inspect | 查看某个数据卷的详情 | docs.docker.com |
docker volume prune | 清除数据卷 | docker volume prune |
注意:容器与数据卷的挂载要在创建容器时配置,对于创建好的容器,是不能设置数据卷的。而且创建容器的过程中,数据卷会自动创建。
演示数据卷的基本操作:
查看数据卷:
docker volume ls
命令:
[root@localhost volumes]# docker volume ls
创建数据卷:
docker volume create
命令:
[root@localhost volumes]# docker volume create aaa
查看数据卷的详细信息:
docker volume inspect aaa
命令:
[root@localhost volumes]# docker volume inspect aaa
删除指定的数据卷:
docker volume rm
命令:
[root@localhost volumes]# docker volume rm aaa
演示nginx的html目录挂载:
# 1,首先创建容器并指定数据卷,注意通过 -v 参数来指定数据卷。
docker run -d --name nginx -p 80:80 -v html:/usr/share/nginx/html nginx
#2,查看数据卷。
docker volume ls
# 结果
DRIVER VOLUME NAME
local dd1539c52e482dceefde783aadc8565ca79088a5a89b9d4f1729f07162488a03
local html#3,查看数据卷的详情。
docker volume inspect html
# 结果
[
{
"CreatedAt": "2024-05-17T19:57:08+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/html/_data",
"Name": "html",
"Options": null,
"Scope": "local"
}
]#4.查看/var/lib/docker/volumes/html/_data目录
ll /var/lib/docker/volumes/html/_data
# 可以看到与nginx的html目录内容一样,结果如下:
总用量 8
-rw-r--r--. 1 root root 497 12月 28 2021 50x.html
-rw-r--r--. 1 root root 615 12月 28 2021 index.html# 5.进入该目录,并随意修改index.html内容
cd /var/lib/docker/volumes/html/_data
vi index.html# 6.打开(浏览器)页面,查看效果
# 7.进入容器内部,查看/usr/share/nginx/html目录内的文件是否变化
docker exec -it nginx bash
1,创建nginx容器:
命令:
[root@localhost ~]# docker run -d --name nginx -p 80:80 -v html:/usr/share/nginx/html nginx
2,查看数据卷:
命令:
[root@localhost ~]# docker volume ls
3,查看数据卷详情:
命令:
[root@localhost ~]# docker volume inspect html
4,查看宿主机关联的nginx的html目录:
5,修改index.html目录:
命令:
[root@localhost ~]# cd /var/lib/docker/volumes/html/_data
[root@localhost _data]# ll
总用量 8
-rw-r--r--. 1 root root 497 12月 28 2021 50x.html
-rw-r--r--. 1 root root 615 12月 28 2021 index.html
[root@localhost _data]# vi index.html
6,打开页面看效果:
7,到容器里面看看变化:
可以看到我们修改的内容了
上面的指令太复杂了,下面的步骤简单快捷一些:
#1,先进入到容器中
docker exec -it nginx bash
#2,然后找到nginx的html目录cd /usr/share/nginx/html
命令:
[root@localhost ~]# docker exec -it nginx bash
root@e459330d6efe:/# cd /usr/share/nginx/html
2.2.3 挂载本地目录或文件:
可以发现,数据卷的目录结构较深,如果我们去操作数据卷目录会不太方便。在很多情况下,我们会直接将容器目录与宿主机指定目录挂载。挂载语法与数据卷类似:
# 挂载本地目录
-v 本地目录:容器内目录
# 挂载本地文件
-v 本地文件:容器内文件
注意:本地目录或文件必须以 / 或 ./开头,如果直接以名字开头,会被识别为数据卷名而非本地目录名。
举例:
-v mysql:/var/lib/mysql # 会被识别为一个数据卷叫mysql,运行时会自动创建这个数据卷
-v ./mysql:/var/lib/mysql # 会被识别为当前目录下的mysql目录,运行时如果不存在会创建目录
演示创建MySQL容器,并完成本地目录挂载:
挂载
/root/mysql/data
到容器内的/var/lib/mysql
目录挂载
/root/mysql/init
到容器内的/docker-entrypoint-initdb.d
目录(初始化的SQL脚本目录)挂载
/root/mysql/conf
到容器内的/etc/mysql/conf.d
目录(这个是MySQL配置文件目录)
1,看看我们已经准备好的mysql目录里面有,conf和init两个文件夹,里面分别是一个hm.cnf和hmall.sql的脚本。
mysql目录:
conf目录:
init 目录:
其中,hm.cnf 主要是配置了MySQL的默认编码,改为utf8mb4;而`hmall.sql`则是后面我们要用到的项目的初始化SQL脚本。
2,把提前准备好的mysql文件夹,上传到虚拟机的/root目录下
可以看到我们已经上传成功了
3,接下来开始演示本地目录的挂载:
#1,先删除原有的MySQL容器
docker rm -f mysql
#2,2.进入root目录
# 3.创建并运行新mysql容器,挂载本地目录
# 4.查看root目录,可以发现~/mysql/data目录已经自动创建好了
# 查看data目录,会发现里面有大量数据库数据,说明数据库完成了初始化
ls -l data
# 5.查看MySQL容器内数据
# 5.1.进入MySQLdocker exec -it mysql mysql -uroot -proot
# 5.2.查看编码表
show variables like "%char%";# 5.3.结果,发现编码是utf8mb4没有问题
# 6.查看数据
# 6.1.查看数据库
show databases;# 6.2.切换到hmall数据库
use hmall;# 6.3.查看表
show tables;# 6.4.查看address表数据
select * from address;
1,删除原来的容器:
命令:
[root@localhost ~]# docker rm -f mysql
2,进入到/root目录:
命令:
[root@localhost /]# cd /root
3.创建并运行新mysql容器,挂载本地目录
docker run -d \
--name mysql \
-p 3306:3306 \
-e TZ=Asia/Shanghai \
-e MYSQL_ROOT_PASSWORD=root \
-v ./mysql/data:/var/lib/mysql \
-v ./mysql/conf:/etc/mysql/conf.d \
-v ./mysql/init:/docker-entrypoint-initdb.d \
mysql
命令:
[root@localhost ~]# docker run -d \
> --name mysql \
> -p 3306:3306 \
> -e TZ=Asia/Shanghai \
> -e MYSQL_ROOT_PASSWORD=root \
> -v ./mysql/data:/var/lib/mysql \
> -v ./mysql/conf:/etc/mysql/conf.d \
> -v ./mysql/init:/docker-entrypoint-initdb.d \
> mysql
4,查看root目录,可以发现~/mysql/data目录已经自动创建好了
查看data
5,查看MySQL容器内数据
5.1进入mysql
docker exec -it mysql mysql -uroot -proot
5.2查看编码表:
show variables like "%char%";
命令:
mysql> show variables like "%char%";
6 查看数据
6.1查看数据库:
show databases;
命令:
mysql> show databases;
6.3查看表:
命令:
mysql> show tables;
6.4查看表中的数据:
命令:
mysql> select * from address;
2.3自定义镜像:
前面我们一直在使用别人准备好的镜像,那如果我要部署一个Java项目,把它打包为一个镜像该怎么做呢?
2.3.1,镜像结构:
自定义镜像本质就是依次准备好程序运行的基础环境、依赖、应用本身、运行配置等文件,并且打包而成。
举个例子,我们要从0部署一个Java应用,大概流程是这样:
准备一个linux服务(CentOS或者Ubuntu均可)
安装并配置JDK
上传Jar包
运行jar包
那因此,我们打包镜像也是分成这么几步:
准备Linux运行环境(java项目并不需要完整的操作系统,仅仅是基础运行环境即可)
安装并配置JDK
拷贝jar包
配置启动脚本
上述步骤中的每一次操作其实都是在生产一些文件(系统运行环境、函数库、配置最终都是磁盘文件),所以镜像就是一堆文件的集合。
上述步骤中的每一次操作其实都是在生产一些文件(系统运行环境、函数库、配置最终都是磁盘文件),所以镜像就是一堆文件的集合。
但需要注意的是,镜像文件不是随意堆放的,而是按照操作的步骤分层叠加而成,每一层形成的文件都会单独打包并标记一个唯一id,称为Layer(层)。这样,如果我们构建时用到的某些层其他人已经制作过,就可以直接拷贝使用这些层,而不用重复制作。
2.3.2,Dockerfile
而这种记录镜像结构的文件就称为**Dockerfile**,其对应的语法可以参考官方文档:
https://docs.docker.com/engine/reference/builder/
比较常用的有:
指令 | 说明 | 示例 |
---|---|---|
FROM | 指定基础镜像 | FROM centos:6 |
ENV | 设置环境变量,可在后面指令使用 | ENV key value |
COPY | 拷贝本地文件到镜像的指定目录 | COPY ./xx.jar /tmp/app.jar |
RUN | 执行Linux的shell命令,一般是安装过程的命令 | RUN yum install gcc |
EXPOSE | 指定容器运行时监听的端口,是给镜像使用者看的 | EXPOSE 8080 |
ENTRYPOINT | 镜像中应用的启动命令,容器运行时调用 | ENTRYPOINT java -jar xx.jar |
例如,要基于Ubuntu镜像来构建一个Java应用,其Dockerfile内容如下:
# 指定基础镜像
FROM ubuntu:16.04
# 配置环境变量,JDK的安装目录、容器内时区
ENV JAVA_DIR=/usr/local
ENV TZ=Asia/Shanghai
# 拷贝jdk和java项目的包
COPY ./jdk8.tar.gz $JAVA_DIR/
COPY ./docker-demo.jar /app.jar
# 设定时区
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 安装JDK
RUN cd $JAVA_DIR \
&& tar -xf ./jdk8.tar.gz \
&& mv ./jdk1.8.0_144 ./java8
# 配置环境变量
ENV JAVA_HOME=$JAVA_DIR/java8
ENV PATH=$PATH:$JAVA_HOME/bin
# 指定项目监听的端口
EXPOSE 8080
# 入口,java项目的启动命令
ENTRYPOINT ["java", "-jar", "/app.jar"]
我们思考一下:以后我们会有很多很多java项目需要打包为镜像,他们都需要Linux系统环境、JDK环境这两层,只有上面的3层不同(因为jar包不同)。如果每次制作java镜像都重复制作前两层镜像,是不是很麻烦。
所以,就有人提供了基础的系统加JDK环境,我们在此基础上制作java镜像,就可以省去JDK的配置了。
# 基础镜像
FROM openjdk:11.0-jre-buster
# 设定时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 拷贝jar包
COPY docker-demo.jar /app.jar
# 入口
ENTRYPOINT ["java", "-jar", "/app.jar"]
这样是不是就简单多了
2.3.3,构建镜像:
当Dockerfile文件写好以后,就可以利用命令来构建镜像了。
我们看到在DockerFile,需要依赖openjdk,所以我们需要提前准备好,jdk。
先把我们电脑里面的jdk上传到linux中
加载为linux的镜像
然后我们可以看到已经有了,这个jdk了。
在今天的资料中,我们准备好了一个demo项目及对应的Dockerfile:
1,首先我们先将上述资料提供的docker-demo.jar 和Dockerfile拷贝到 /root/demo目录下:
看看我们自己创建的demo文件夹:
2,进入到demo文件夹里面:
# 进入镜像目录
cd /root/demo
命令:
[root@localhost ~]# cd /root/demo
[root@localhost demo]# ll
3,把我们的docker-demo.jar 和Dockerfile拷贝到 /root/demo目录下
4,开始构建:
# 开始构建
docker build -t docker-demo:1.0 .
命令:
[root@localhost demo]# docker build -t docker-demo:1.0 .
5,查看镜像:
命令:
[root@localhost ~]# docker images
6,尝试并运行该容器:
# 1.创建并运行容器
docker run -d --name dd -p 8080:8080 docker-demo:1.0
# 2.查看容器
dps
# 结果
CONTAINER ID IMAGE PORTS STATUS NAMES
78a000447b49 docker-demo:1.0 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp Up 2 seconds dd
f63cfead8502 mysql 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp Up 2 hours mysql# 3.访问
curl localhost:8080/hello/count
# 结果:
<h5>欢迎访问商城, 这是您第1次访问<h5>
6.1,创建并运行容器:
命令:
[root@localhost ~]# docker run -d --name dd -p 8080:8080 docker-demo:1.0
6.2查看容器:
命令:
[root@localhost ~]# docker ps
6.3访问:
命令:
[root@localhost ~]# curl localhost:8080/hello/count
在浏览器访问:
2.4,网络:
前面我们创建了一个Java项目的容器,而Java项目往往需要访问其它各种中间件,例如MySQL、Redis等。现在,我们的容器之间能否互相访问呢?我们来测试一下。
首先,我们查看下MySQL容器的详细信息,重点关注其中的网络IP地址:
# 1.用基本命令,寻找Networks.bridge.IPAddress属性
docker inspect mysql
# 也可以使用format过滤结果
docker inspect --format='{{range .NetworkSettings.Networks}}{{println .IPAddress}}{{end}}' mysql
# 得到IP地址如下:
172.17.0.2
# 2.然后通过命令进入dd容器
docker exec -it dd bash# 3.在容器内,通过ping命令测试网络
ping 172.17.0.2
# 结果
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.053 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.059 ms
64 bytes from 172.17.0.2: icmp_seq=3 ttl=64 time=0.058 ms
1,用基本命令:
[root@localhost ~]# docker inspect mysql
详情:
1.2,查看过滤后的结果:
命令:
[root@localhost ~]# docker inspect --format='{{range .NetworkSettings.Networks}}{{println .IPAddress}}{{end}}' mysql
2,然后通过命令进入dd容器:
命令:
[root@localhost ~]# docker exec -it dd bash
root@a3d78ff4eb62:/#
3.在容器内,通过ping命令测试网络
文章来源:https://www.toymoban.com/news/detail-849063.html
命令:
root@a3d78ff4eb62:/# ping 172.17.0.2
三,部署项目:
到了这里,关于Docker快速入门和部署项目的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!