热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:Docker镜像创建数据管理与网络通信

本章架构:Docker的创建方法Docker的数据管理Docker的网络通信

本章架构:


  • Docker的创建方法

  • Docker的数据管理

  • Docker的网络通信


Docker镜像除了是Docker的核心技术之外,也是应用发布的标准格式。一个完整的Docker镜像可以支撑一个Docker容器的运行,在Docker的整个使用过程中,进入一个已经定型的容器后,就可以在容器中进行操作,最常见的操作技术在容器中安装应用服务,如果要把已经安装的服务进行迁移,就要把环境及搭建的服务生成新的镜像。下面介绍这三种创建镜像的方法:

一、Docker镜像的创建方法


1.基于已有镜像创建

基于已有镜像创建主要使用docker commit 命令。其实质就是把一个容器里面运行的程序及该程序的运行环境打包起来生成新的镜像。

命令格式:

docker commit 【选项】容器id/名称 仓库名称:【标签】

常用选项:
-m:说明信息
-a:作者信息
-p:生成过程中停止容器的运行

方法如下:
(1)使用镜像创建一个新的容器,并进行修改。

[root@localhost ~]# docker images //查看本地的Docker镜像
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/centos latest 0f3e07c0138f 6 weeks ago 220 MB
[root@localhost ~]# docker run --privileged -d -it --name centos docker.io/centos init
794df7dc4cebeb43afb2a1d7cf424578a4f10c2344bcdb7208d6632609ce087c
//使用centos镜像生成一个名为centos的容器,让容器以root的身份权限加载init守护进程
[root@localhost ~]# docker exec -it centos /bin/bash //指定一个shell进入容器
[root@794df7dc4ceb /]# yum -y install vsftpd //在容器中安装一个ftp服务
[root@794df7dc4ceb /]# systemctl start vsftpd //安装完成后,启动服务
[root@794df7dc4ceb /]# exit //退出容器

(2)使用“docker commit”命令创建一个新的镜像。

[root@localhost ~]# docker commit -m "vsftpd" -a "xxf" centos xxf:ftp
sha256:ccba2c39b90a56373139196c3dc079b6df5c7f4f280bc35a7f1abf578962b52
//基于刚才创建的容器生成一个新的镜像,名称为lzj:ftp

(3)创建完成后,查看本地镜像是否已经有新生成的镜像。

[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
lzj ftp ccba2c39b90a 57 seconds ago 279 MB
docker.io/centos latest 75835a67d134 13 months ago 200 MB

2.基于本地模板创建

通过导入操作系统模板文件可以生成镜像,模板可以从OPENVZ开源项目下载,下载地址为:https://wiki.openvz.org/Download/template/precreated ,优先使用OPENVZ开源项目那个链接。

其实,就把使用“docker load <文件名”将一个文件导入成镜像而已,这里就不多介绍了!

3.基于Dockerfile创建

除了手动生成docker镜像之外,还可以使用Dockerfile自动生成镜像。Dockerfile是由一组指令组成的文件,其中每条指令对应Linux中的一条命令,Docker程序将读取Dockerfile中的指令生成指定镜像。

Dockerfile结构大致分为4个部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时候执行指令。Dockerfile每行支持一条指令,每条指令可携带多个参数,支持使用以“#”号开头的注释。

一个简单的小例子:

[root@localhost ~]# vim Dokerfile
FROM centos
#第一行必须指明基于的基础镜像
MAINTAINER The CentOS project
#维护该镜像的用户信息
RUN yum -y update
RUN yum -y install openssh-server
RUN sed -i ‘s/UsePAM yes/UsePAM no/g‘ /etc/ssh/sshd_config
RUN ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key
RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
#镜像操作指令
EXPOSE 22
#开启22端口
CMD ["/usr/sbin/sshd","-D"]
#启动容器时执行指令

在编写Dockerfile时,有严格的格式需要遵循:第一行必须使用FROM指令指明所基于的镜像名称;之后再使用MAINTAINER指令说明维护该镜像的用户信息;然后是镜像操作相关指令,如RUN指令,每运行一条指令,都会给基础镜像添加新的一层;最后使用CMD指令来指定启动容器时要运行的命令操作。
Dockerfile有十几条命令可以用于构建镜像;其中常见的指令如下表:
技术图片

案例:

1.建立工作目录

[root@localhost ~]# mkdir apache
[root@localhost ~]# cd apache/

2.创建并编写Dockerfile文件

[root@localhost apache]# vim Dockerfile
FROM docker.io/centos
MAINTAINER The CentOS Projects
RUN yum -y update
RUN yum -y install httpd
EXPOSE 80
ADD index.html /var/www/html/index.html
ADD run.sh /run.sh
RUN chmod 775 /run.sh
RUN systemctl disable httpd
CMD ["/run.sh"]
此Dockerfile文件使用的基础镜像是centos,所以要保证首先获取此基础镜像,使用“docker pull docker.io/centos” 获取镜像,之后的容器运行才会有效;

3.编写执行脚本内容

[root@localhost apache]# vim run.sh
#!/bin/bash
rm -rf /run/httpd/* //清理httpd的缓存
exec /usr/sbin/apachectl -D FOREGROUND //启动apache服务
//启动容器时,进程、脚本必须在前台启动

4.创建测试页面

[root@localhost apache]# echo "weclome" > index.html //创建首页文件
[root@localhost apache]# ls
Dockerfile index.html run.sh //这三个文件最好是在同一目录下

5.使用Dockerfile生成镜像

编写完成Dockerfile及相关内容后,可以通过“docker build”命令来创建镜像。

命令格式:

docker build [选项] 路径
//常用选项“-t”指定镜像的标签信息
[root@localhost apache]# docker build -t httpd:centos .
使用刚才创键的Dockerfile 自动生成镜像,最后必须要有一个“.”表示当前路径

6.使用新的镜像运行容器

[root@localhost ~]# docker run -d -p 12345:80 httpd:centos
ee9adf324443b006ead23f2d9c71f86d1a4eb73358fb684ee3a2d058a0ac4243
//使用新生成的镜像加载到容器中运行
//“-p”选项实现从本地端口12345到容器中80端口的映射

[root@localhost ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ee9adf324443 httpd:centos "/run.sh" About a minute ago Up About a minute 0.0.0.0:12345->80/tcp admiring_bardeen
//查看该镜像已经在容器中的状态

验证访问结果:
技术图片

7.将镜像上传到仓库中
随着创键的镜像增多,就需要有一个保存镜像的地方,这就是仓库。目前有两种仓库:公共仓库和私有仓库。最方便的就是使用公共仓库上传和下载,下载公共仓库的镜像是不需要注册的,但是上传时,是需要注册的。下面介绍如何创建私有仓库。

方法如下:
(1)在构建私有仓库的服务器上下载registry镜像;


[root@localhost ~]# docker pull registry

(2)修改配置文件指定私有路径的URL,否则在自定义的私有仓库中上传镜像时会报错,修改完毕后重启Docker服务。


[root@localhost ~]# vim /etc/sysconfig/docker

技术图片


[root@localhost ~]# systemctl restart docker //重启docker服务

(3)使用下载好的registry镜像启动一个容器,默认情况下仓库存放于容器内的/tmp/registry目录下,使用“-v”选项可以将本地目录挂载到容器内的/tmp/registry目录下使用,这样就不怕容器被删除后镜像也会随之丢失。


[root@localhost ~]# docker run -d -p 5000:5000 -v /data/registry:/tmp/registry registry
d5ecd77aa852df0c67935888009116325025cab49b7a4807196d251ce35a2b3b
//本地的目录不用事先创建

(4)使用“docker tag”命令将要上传的镜像docker.io/registry 标记为

192.168.1.1:5000/registry 。
[root@localhost ~]# docker tag docker.io/registry 192.168.1.1:5000/registry
[root@localhost ~]# docker push 192.168.1.1:5000/registry //上传本地镜像到服务器上
[root@localhost registry]# curl -XGET http://192.168.1.1:5000/v2/_catalog
{"repositories":["registry"]} //查看仓库类的镜像
[root@localhost registry]# curl -XGET http://192.168.1.1:5000/v2/registry/tags/list
{"name":"registry","tags":["latest"]} //获取镜像的标签列表
[root@localhost registry]# docker rmi -f 192.168.1.1:5000/registry //删除本地镜像进行测试
[root@localhost registry]# docker pull 192.168.1.1:5000/registry //从本地私有仓库进行下载
[root@localhost registry]# docker images | grep 192.168.1.1:5000/registry
192.168.1.1:5000/registry latest f32a97de94e1 8 months ago 25.8 MB
//本地进行验证

私有仓库搭建完成,验证成功!

二、docker的数据管理

在docker中,为了方便查看容器内产生的数据或者将多个容器之间的数据实现共享,会涉及到容器的数据管理操作,管理docker容器中的数据主要有两种方式:数据卷和数据卷容器。

1、数据卷
数据卷是一个供容器使用的特殊目录,位于容器中,可将宿主机的目录挂载到数据卷上,对数据卷的修改操作立即可见,并且更新数据不会影响镜像,从而实现数据在宿主机与容器之间的迁移,数据卷的使用类似于Linux下对目录进行的mount挂载操作(注意:是将宿主机本地的目录挂载到容器中,举例:若宿主机本地/data目录挂载的是/dev/sdb1,那么要将/data做数据卷映射时,容器中指定的目录使用的文件系统也是/dev/sdb1,我不知道这样解释,你们能不能理解它的工作原理)。

挂载宿主机目录作为数据卷举例:

使用-v选项可以创建数据卷(只是运行容器时,创建一个目录),创建数据卷的同时将宿主机的目录挂载到数据卷上使用,以实现宿主机与容器之间的数据迁移。

需要注意的是,宿主机本地目录的路径必须是使用绝对路径,如果路径不存在,Docker会自动创建相应的路径。

[root@localhost ~]# docker run -d -p 5000:5000 -v /data/registry/:/tmp/registry docker.io/registry
#这是运行了一个私有仓库的容器,其中-p是端口映射的选项,这里不做解释。
# -v才是目录映射,将本地/data/registry/目录映射到容器中的/tmp/registry目录。
#然后容器中的/tmp/registry目录下的内容就和宿主机的/data/registry/内容一样了。
[root@localhost ~]# df -hT /data/registry/ #先查看本地/data/registry/ 挂载的文件系统
文件系统 类型 容量 已用 可用 已用% 挂载点
node4:dis-stripe fuse.glusterfs 80G 130M 80G 1% /data/registry
[root@localhost ~]# docker exec -it a6bf726c612b /bin/sh #进入私有仓库的容器中,该容器没有/bin/bash,所以使用的是/bin/sh。
/ # df -hT /tmp/registry/ #查看发现,该目录挂载的和宿主机挂载的文件系统是同一个,说明没问题。
Filesystem Type Size Used Available Use% Mounted on
node4:dis-stripe fuse.glusterfs
80.0G 129.4M 79.8G 0% /tmp/registry

2、数据卷容器
如果需要在容器之间共享一些数据,最简单的方法就是使用数据卷容器。数据卷容器就是一个普通的容器,专门提供数据卷给其他容器挂载使用。使用方法如下:首先,需要创建一个容器作为数据卷容器,之后在其他容器创建时用--volumes-from挂载数据卷容器中的数据卷使用。

容器卷创建及使用举例:

[root@localhost ~]# docker run -itd --name datasrv -v /data1 -v /data2 docker.io/sameersbn/bind /bin/bash
#创建运行一个容器,容器名为datasrv,并创建两个数据卷:data1和data2。
d9e578db8355da35637d2cf9b0a3406a647fe8e70b2df6172ab41818474aab08
[root@localhost ~]# docker exec -it datasrv /bin/bash #进入创建的容器
root@d9e578db8355:/# ls | grep data #查看是否有对应的数据卷
data1
data2
[root@localhost ~]# docker run -itd --volumes-from datasrv --name ftpsrv docker.io/fauria/vsftpd /bin/bash
#运行一个名为ftpsrv的容器,使用--volumes-from来将datasrv容器中的数据卷挂载到这个ftpsvr新容器上。
eb84fa6e85a51779b652e0058844987c5974cf2a66d1772bdc05bde30f8a254f
[root@localhost ~]# docker exec -it ftpsrv /bin/bash #进入新创建的容器
[root@eb84fa6e85a5 /]# ls | grep data #查看新的容器是否可以看到datasrv提供的数据卷
data1
data2
[root@eb84fa6e85a5 /]# echo " data volumes test" > /data1/test.txt #在ftpsrv容器中向data1目录写入文件进行测试
[root@eb84fa6e85a5 /]# exit #退出该容器
exit
[root@localhost ~]# docker exec -it datasrv /bin/bash #进入提供数据卷的datasrv容器
root@d9e578db8355:/# cat /data1/test.txt #可以看到刚在ftpsrv容器创建的文件,OK。
data volumes test

注意,生产环境中最注重的就是存储的可靠性,以及存储的可动态扩展性,一定要在做数据卷时考虑到这一点,在这方面比较出色的还要数GFS文件系统了,我上面只是做了简单的配置,若在生产环境中,一定要好好考虑,就比如上面做的镜像卷容器,就可以在宿主机本地挂载GFS文件系统,然后创建镜像卷容器时,将挂载GFS的目录映射到容器中的镜像卷,这样才是一个合格的镜像卷容器。

三、docker网络通信

1、端口映射
docker提供了映射容器端口到宿主机和容器互联机制来为容器提供网络服务。

在启动容器的时候,如果不指定对应的端口,在容器外是无法通过网络来访问容器内的服务的。docker提供端口映射机制来将容器内的服务提供给外部网络访问,实质上就是将宿主机的端口映射到容器中,使外部网络访问宿主机的端口可访问容器内的服务。

实现端口映射,需要在运行docker run命令时使用-P(大写)选项实现随机映射,Docker一般会随机映射到一个端口访问在49000~49900的端口到容器内部开放的网络端口,但不是绝对的,也有例外情况不会映射到这个范围;也可以使用在运行docker run命令时使用-p(小写)选项实现指定要映射的端口(常用此种方法)。

端口映射举例:

[root@localhost ~]# docker run -d -P docker.io/sameersbn/bind #随机映射端口
9b4b7c464900df3b766cbc9227b21a3cad7d2816452c180b08eac4f473f88835
[root@localhost ~]# docker run -itd -p 68:67 docker.io/networkboot/dhcpd /bin/bash
#将容器中的67端口映射到宿主机的68端口
6f9f8125bcb22335dcdb768bbf378634752b5766504e0138333a6ef5c57b7047
[root@localhost ~]# docker ps -a #查看发现没问题咯
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6f9f8125bcb2 docker.io/networkboot/dhcpd "/entrypoint.sh /b..." 2 seconds ago Up 1 second 0.0.0.0:68->67/tcp keen_brattain
9b4b7c464900 docker.io/sameersbn/bind "/sbin/entrypoint...." 4 minutes ago Up 4 minutes 0.0.0.0:32768->53/udp, 0.0.0.0:32769->53/tcp, 0.0.0.0:32768->10000/tcp coc_gates
#此时,访问宿主机的68端口就相当于访问第一个容器的67端口;访问宿主机的32768端口,就相当于访问容器的53端口。

2、容器互联
容器互联是通过容器的名称在容器之间建立一条专门的网络通信隧道从而实现的互联。简单说,就是会在源容器和接收容器之间建立一条隧道,接收容器可以看到源容器指定的信息。

在运行docker run命令时,使用--link选项实现容器之间的互联通信,格式如下:


--link name: alias #其中name是要连接的容器名称,alias是这个连接的别名。

容器互联是通过容器的名称来执行的,--name选项可以给容器创建一个友好的名称,这个名称是唯一的,如果已经命名了一个相同名称的容器,当要再次使用这个名称的时候,需要先使用docker rm命令来删除之前创建的同名容器。

容器互联举例:

[root@localhost ~]# docker run -tid -P --name web1 docker.io/httpd /bin/bash #运行容器web1
c88f7340f0c12b9f5228ec38793e24a6900084e58ea4690e8a847da2cdfe0b
[[root@localhost ~]# docker run -tid -P --name web2 --link web1:web1 docker.io/httpd /bin/bash
#运行容器web2,并关联web1容器
c7debd7809257c6375412d54fe45893241d2973b7af1da75ba9f7eebcfd4d652
[root@localhost ~]# docker exec -it web2 /bin/bash #进入web2容器
root@c7debd780925:/usr/local/apache2# cd
root@c7debd780925:~# ping web1 #对web1进行ping测试
bash: ping: command not found #sorry,提示没有ping命令,下载一个咯
root@c7debd780925:~#apt-get update #更新一下
root@c7debd780925:~#apt install iputils-ping #安装ping命令
root@c7debd780925:~#apt install net-tools #这个是安装ifconfig命令,可以不安装,我这里只是做个笔记
root@c7debd780925:~# ping web1 #再对web1进行ping测试
PING web1 (172.17.0.2) 56(84) bytes of data.
64 bytes from web1 (172.17.0.2): icmp_seq=1 ttl=64 time=0.079 ms
64 bytes from web1 (172.17.0.2): icmp_seq=2 ttl=64 time=0.114 ms
..............#省略部分内容
#ping通了,所以可以说这两个容器肯定是互联的咯
#若此时又创建了web3这个新容器,要同时和web1、web2进行互联,命令如下:
[root@localhost ~]# docker run -dit -P --name web3 --link web1:web1 --link web2:web2 docker.io/httpd /bin/bash
#运行容器时,关联web1和web2。
#以下是进入web3
[root@localhost ~]# docker exec -it web3 /bin/bash
root@433d5be6232c:/usr/local/apache2# cd
#以下是安装ping命令
root@433d5be6232c:~# apt-get update
root@433d5be6232c:~# apt install iputils-ping
#以下是分别对web1,web2进行ping测试
root@433d5be6232c:~# ping web1
PING web1 (172.17.0.2) 56(84) bytes of data.
64 bytes from web1 (172.17.0.2): icmp_seq=1 ttl=64 time=0.102 ms
64 bytes from web1 (172.17.0.2): icmp_seq=2 ttl=64 time=0.112 ms
..............#省略部分内容
root@433d5be6232c:~# ping web2
PING web2 (172.17.0.3) 56(84) bytes of data.
64 bytes from web2 (172.17.0.3): icmp_seq=1 ttl=64 time=0.165 ms
64 bytes from web2 (172.17.0.3): icmp_seq=2 ttl=64 time=0.115 ms
..............#省略部分内容
#OK,没问题

———————— 本文至此结束,感谢阅读 ————————


推荐阅读
  • 在Kubernetes上部署JupyterHub的步骤和实验依赖
    本文介绍了在Kubernetes上部署JupyterHub的步骤和实验所需的依赖,包括安装Docker和K8s,使用kubeadm进行安装,以及更新下载的镜像等。 ... [详细]
  • 基于PgpoolII的PostgreSQL集群安装与配置教程
    本文介绍了基于PgpoolII的PostgreSQL集群的安装与配置教程。Pgpool-II是一个位于PostgreSQL服务器和PostgreSQL数据库客户端之间的中间件,提供了连接池、复制、负载均衡、缓存、看门狗、限制链接等功能,可以用于搭建高可用的PostgreSQL集群。文章详细介绍了通过yum安装Pgpool-II的步骤,并提供了相关的官方参考地址。 ... [详细]
  • 成功安装Sabayon Linux在thinkpad X60上的经验分享
    本文分享了作者在国庆期间在thinkpad X60上成功安装Sabayon Linux的经验。通过修改CHOST和执行emerge命令,作者顺利完成了安装过程。Sabayon Linux是一个基于Gentoo Linux的发行版,可以将电脑快速转变为一个功能强大的系统。除了作为一个live DVD使用外,Sabayon Linux还可以被安装在硬盘上,方便用户使用。 ... [详细]
  • 安装mysqlclient失败解决办法
    本文介绍了在MAC系统中,使用django使用mysql数据库报错的解决办法。通过源码安装mysqlclient或将mysql_config添加到系统环境变量中,可以解决安装mysqlclient失败的问题。同时,还介绍了查看mysql安装路径和使配置文件生效的方法。 ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • Windows下配置PHP5.6的方法及注意事项
    本文介绍了在Windows系统下配置PHP5.6的步骤及注意事项,包括下载PHP5.6、解压并配置IIS、添加模块映射、测试等。同时提供了一些常见问题的解决方法,如下载缺失的msvcr110.dll文件等。通过本文的指导,读者可以轻松地在Windows系统下配置PHP5.6,并解决一些常见的配置问题。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • CentOS 6.5安装VMware Tools及共享文件夹显示问题解决方法
    本文介绍了在CentOS 6.5上安装VMware Tools及解决共享文件夹显示问题的方法。包括清空CD/DVD使用的ISO镜像文件、创建挂载目录、改变光驱设备的读写权限等步骤。最后给出了拷贝解压VMware Tools的操作。 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
  • CEPH LIO iSCSI Gateway及其使用参考文档
    本文介绍了CEPH LIO iSCSI Gateway以及使用该网关的参考文档,包括Ceph Block Device、CEPH ISCSI GATEWAY、USING AN ISCSI GATEWAY等。同时提供了多个参考链接,详细介绍了CEPH LIO iSCSI Gateway的配置和使用方法。 ... [详细]
  • 树莓派语音控制的配置方法和步骤
    本文介绍了在树莓派上实现语音控制的配置方法和步骤。首先感谢博主Eoman的帮助,文章参考了他的内容。树莓派的配置需要通过sudo raspi-config进行,然后使用Eoman的控制方法,即安装wiringPi库并编写控制引脚的脚本。具体的安装步骤和脚本编写方法在文章中详细介绍。 ... [详细]
  • 本文介绍了在MacOS系统上安装MySQL的步骤,并详细说明了如何设置MySQL服务的开机启动和如何修改MySQL的密码。通过下载MySQL的macos版本并按照提示一步一步安装,在系统偏好设置中可以找到MySQL的图标进行设置。同时,还介绍了通过终端命令来修改MySQL的密码的具体操作步骤。 ... [详细]
  • 本文介绍了在CentOS 6.4系统中更新源地址的方法,包括备份现有源文件、下载163源、修改文件名、更新列表和系统,并提供了相应的命令。 ... [详细]
  • Annotation的大材小用
    为什么80%的码农都做不了架构师?最近在开发一些通用的excel数据导入的功能,由于涉及到导入的模块很多,所以开发了一个比较通用的e ... [详细]
author-avatar
丽sd园印章
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有