zoukankan      html  css  js  c++  java
  • 如何构建Docker镜像

    构建Docker 镜像有如下两种方法:

    (一)使用docker commit命令。
    (二)使用docker build命令和 Dockerfile 文件。
    在这里并不推荐使用docker commit来构建镜像,而应该使用更灵活、更强大的Dockerfile来构建Docker镜像。但是为了对Docker有一个更全面的了解,还是会先介绍以下如何使用docker commit构建Docker镜像。之后将重点介绍Docker所推荐的镜像构建方法:编写Dockerfile之后使用docker build命令。

    一般来说,我们不是真正的“创建”新镜像,而是基于一个已有的基础镜像,如ubuntu或centos等,构建新镜像而已。如果真的想从零构建一个全新的镜像,也可以参考https://docs.docker.com/engine/userguide/eng-image/baseimages/

    1.通过commit命令创建镜像
    docker commit 构建镜像可以想象为是在往版本控制系统里提交变更。我们先创建一个容器,并在容器里做出修改,就像修改代码一样,最后将修改提交为一个镜像。

    [root@docker ~]# docker run -it ubuntu:14.04 /bin/bash 
    Unable to find image 'ubuntu:14.04' locally
    14.04: Pulling from library/ubuntu
    e082d4499130: Pull complete 
    371450624c9e: Pull complete 
    c8a555b3a57c: Pull complete 
    1456d810d42e: Pull complete 
    Digest: sha256:6612de24437f6f01d6a2988ed9a36b3603df06e8d2c0493678f3ee696bc4bb2d
    Status: Downloaded newer image for ubuntu:14.04
    root@e296b32b1656:/# apt-get -y update
    Get:1 http://security.ubuntu.com trusty-security InRelease [65.9 kB]
    Ign http://archive.ubuntu.com trusty InRelease             
    Get:2 http://archive.ubuntu.com trusty-updates InRelease [65.9 kB]
    Get:3 http://security.ubuntu.com trusty-security/main amd64 Packages [1024 kB]
    Get:4 http://archive.ubuntu.com trusty-backports InRelease [65.9 kB]           
    Get:5 http://archive.ubuntu.com trusty Release.gpg [933 B]                     
    Get:6 http://archive.ubuntu.com trusty-updates/main amd64 Packages [1445 kB]
    Get:7 http://security.ubuntu.com trusty-security/restricted amd64 Packages [18.1 kB]
    Get:8 http://security.ubuntu.com trusty-security/universe amd64 Packages [375 kB]
    Get:9 http://security.ubuntu.com trusty-security/multiverse amd64 Packages [4723 B]
    Get:10 http://archive.ubuntu.com trusty-updates/restricted amd64 Packages [21.4 kB]
    Get:11 http://archive.ubuntu.com trusty-updates/universe amd64 Packages [667 kB]
    Get:12 http://archive.ubuntu.com trusty-updates/multiverse amd64 Packages [16.1 kB]
    Get:13 http://archive.ubuntu.com trusty-backports/main amd64 Packages [14.7 kB]
    Get:14 http://archive.ubuntu.com trusty-backports/restricted amd64 Packages [40 B]
    Get:15 http://archive.ubuntu.com trusty-backports/universe amd64 Packages [52.5 kB]
    Get:16 http://archive.ubuntu.com trusty-backports/multiverse amd64 Packages [1392 B]
    Get:17 http://archive.ubuntu.com trusty Release [58.5 kB]                      
    Get:18 http://archive.ubuntu.com trusty/main amd64 Packages [1743 kB]          
    Get:19 http://archive.ubuntu.com trusty/restricted amd64 Packages [16.0 kB]    
    Get:20 http://archive.ubuntu.com trusty/universe amd64 Packages [7589 kB]      
    Get:21 http://archive.ubuntu.com trusty/multiverse amd64 Packages [169 kB]     
    Fetched 13.4 MB in 44s (299 kB/s)                                              
    Reading package lists... Done
    root@e296b32b1656:/# apt-get install apache2 -y  

    我们启动了一个容器,并在里面安装了Apache。我们会将这个容器作为一个Web服务器来运行,所以我们想把它的当前状态保存下来。这样我们就不必每次都创建一个新容器并再次在里面安装Apache了。为了完成此项工作,需要先使用exit命令从容器里退出,之后再运行docker commit命令:

    Processing triggers for libc-bin (2.19-0ubuntu6.14) ...
    Processing triggers for sgml-base (1.26+nmu4ubuntu1) ...
    Processing triggers for ureadahead (0.100.0-16) ...
    root@e296b32b1656:/# exit
    exit
    [root@docker ~]# docker ps -a
    CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
    e296b32b1656        ubuntu:14.04        "/bin/bash"         6 minutes ago       Exited (0) 6 seconds ago                       wizardly_mayer
    [root@docker ~]# docker commit e296b32b1656 ubuntu14.04:apache2
    sha256:67879dfd562b5486ef9162a249a6210b891d20cd1dea063cfab3f2ef4e97c5d7
    [root@docker ~]# docker images ubuntu14.04:apache2
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    ubuntu14.04         apache2             67879dfd562b        13 seconds ago      216MB  

    在使用docker commit命令中,指定了要提交的修改过的容器的ID(可以通过docker ps命令得到刚创建的容器ID),以及一个目标镜像仓库和镜像名,这里是test/httpd。需要注意的是,docker commit提交的只是创建容器的镜像与容器的当前状态之间有差异的部分,这使得该更新非常轻量。通过docker images 可以查看新创建的镜像信息。

    也可以在提交镜像时指定更多的数据(包括标签)来详细描述所做的修改。

    [root@docker ~]# docker commit -m="new images" --author=“caoss”  e296b32b1656  ubuntu14.04:apache2
    sha256:af41cda7163bade4b99f9bb52150ab622dbbc53c4bc27a3e42455523d7997d4a
    [root@docker ~]#   

    这条命令里,指定了更多的信息选项:

    -m 用来指定创建镜像的提交信息;
    --author 用来列出该镜像的作者信息;
    最后在ubuntu14.04后面增加了一个apache2的标签。
    通过使用docker inspect命令来查看新创建的镜像的详细信息:

    [root@docker ~]# docker inspect ubuntu14.04:apache2
    [
        {
            "Id": "sha256:af41cda7163bade4b99f9bb52150ab622dbbc53c4bc27a3e42455523d7997d4a",
            "RepoTags": [
                "ubuntu14.04:apache2"
            ],
            "RepoDigests": [],
            "Parent": "sha256:390582d83ead410e808a4d1868a5b1a329241132a76565fd69ab23f60c032d81",
            "Comment": "new images",
            "Created": "2019-04-03T08:51:14.691005242Z",
            "Container": "e296b32b1656690667277d7a1a2c24ea59b9389b9f08bc48cf509214cabc3893",

    2.创建Dockerfile文件
    下面将介绍如何通过Dockerfile的定义文件和docker build命令来构建镜像。
    Dockerfile使用基本的基于DSL语法的指令来构建一个Docker镜像,之后使用docker build命令基于该Dockerfile中的指令构建一个新的镜像。

    # mkdir /home/wwwroot
    # cd /home/wwwroot
    # vim Dockerfile

    首先创建一个名为/home/wwwroot的目录用来保存Dockerfile,这个目录就是我们的构建环境(build environment),Docker则称此环境为上下文(context)或者构建上下文(build context)。Docker会在构建镜像时将构建上下文和该上下文中的文件和目录上传到Docker守护进程。这样Docker守护进程就能直接访问你想在镜像中存储的任何代码、文件或者其他数据。这里我们还创建了一个Dockerfile文件,我们将用它构建一个能作为Web服务器的Docker镜像。

    # Version: 1.0.0
    FROM ubuntu:14.04
    MAINTAINER Caoss
    RUN apt-get update
    RUN apt-get install -y nginx
    RUN echo 'Hello, This is  Nginx Container' > /usr/share/nginx/html/index.html
    EXPOSE 80

    Dockerfile由一系列指令和参数组成。每条指令都必须为大写字母,切后面要跟随一个参数。Dockerfile中的指令会按照顺序从上到下执行,所以应该根据需要合理安排指令的顺序。每条指令都会创建一个新的镜像层并对镜像进行提交。Docker大体上按照如下流程执行Dockerfile中的指令。

    • Docker从基础镜像运行一个容器。
    • 执行第一条指令,对容器进行修改。
    • 执行类似docker commit的操作,提交一个新的镜像层。
    • Docker再基于刚提交的镜像运行一个新的容器。
    • 执行Dockerfile中的下一条命令,直到所有指令都执行完毕。

    Dockerfile也支持注释。以#开头的行都会被认为是注释,如:# Version: 1.0.0这就是个注释

    FROM:

    每个Dockerfile的第一条指令都应该是FROM。FROM指令指定一个已经存在的镜像,后续指令都是将基于该镜像进行,这个镜像被称为基础镜像(base image)。在这里centos:latest就是作为新镜像的基础镜像。也就是说Dockerfile构建的新镜像将以centos:latest操作系统为基础。在运行一个容器时,必须要指明是基于哪个基础镜像在进行构建。

    MAINTAINER:
    MAINTAINER指令,这条指令会告诉Docker该镜像的作者是谁,以及作者的邮箱地址。这有助于表示镜像的所有者和联系方式

    RUN:
    在这些命令之后,我们指定了三条RUN指令。RUN指令会在当前镜像中运行指定的命令。这里我们通过RUN指令更新了APT仓库,安装nginx包,并创建了一个index.html文件。像前面说的那样,每条RUN指令都会创建一个新的镜像层,如果该指令执行成功,就会将此镜像层提交,之后继续执行Dockerfile中的下一个指令。

    默认情况下,RUN指令会在shell里使用命令包装器/bin/sh -c 来执行。如果是在一个不支持shell的平台上运行或者不希望在shell中运行(比如避免shell字符串篡改),也可以使用exec格式的RUN指令,通过一个数组的方式指定要运行的命令和传递给该命令的每个参数:

    RUN ["apt-get", "install", "-y", "nginx"]

    EXPOSE:
    EXPOSE指令是告诉Docker该容器内的应用程序将会使用容器的指定端口。这并不意味着可以自动访问任意容器运行中服务的端口。出于安全的原因,Docker并不会自动打开该端口,而是需要你在使用docker run运行容器时来指定需要打开哪些端口。

    可以指定多个EXPOSE指令来向外部公开多个端口,Docker也使用EXPOSE指令来帮助将多个容器链接,在后面的学习过程中我们会接触到。

    基于Dockerfile构建新镜像 

    执行docker build命令时,Dockerfile中的所有指令都会被执行并且提交,并且在该命令成功结束后返回一个新镜像。

    [root@docker wwwroot]# docker build -t "ubuntu14.04/apache2" .
    Sending build context to Docker daemon  2.048kB
    Step 1/6 : FROM ubuntu:14.04
     ---> 390582d83ead
    Step 2/6 : MAINTAINER Caoss
     ---> Running in 9d7790febf59
    Removing intermediate container 9d7790febf59
     ---> 9fb19fac26ec
    Step 3/6 : RUN apt-get update
     ---> Running in 8b3f2af60de0

     注意:-t 选项为新镜像设置了仓库和名称,这里仓库为ubuntu14.04,镜像名为apache2 建议为自己的镜像设置合适的名字方便以后追踪和管理.

      也可以在构建镜像的过程当中为镜像设置一个标签

    [root@docker wwwroot]# docker build -t "ubuntu14.04/apache2:v1" .

    上面命令中最后的“.”告诉Docker到当前目录中去找Dockerfile文件。也可以指定一个Git仓库地址来指定Dockerfile的位置,这里Docker假设在Git仓库的根目录下存在Dockerfile文件:

    [root@docker wwwroot]# docker build -t "ubuntu14.04/apache2:v1" git@github.com:ubuntu14.04/apache2

    再回到docker build 过程,可以看到构建上下文已经上传到Docker守护进程:

    提示:如果在构建上下文的根目录下存在以.dockerignore命名的文件的话,那么该文件内容会被按行进行分割,每一行都是一条文件过滤匹配模式。这非常像.gitignore文件,该文件用来设置哪些文件不会被上传到构建上下文中去。该文件中模式的匹配规则采用了Go语言中的filepath。

    之后,可以看到Dockerfile中的每条指令会被顺序执行,而作为构建过程中最终结果,返回了新镜像的ID,即45e2610622e5。构建的每一步及其对应指令都会独立运行,并且在输出最终镜像ID之前,Docker会提交每步的构建结果。

    Setting up nginx (1.4.6-1ubuntu3.9) ...
    Processing triggers for libc-bin (2.19-0ubuntu6.14) ...
    Processing triggers for sgml-base (1.26+nmu4ubuntu1) ...
    Removing intermediate container 2bdc766dd213
     ---> ccbba10251e5
    Step 5/6 : RUN echo 'Hello, This is  Nginx Container' > /usr/share/nginx/html/index.html
     ---> Running in 8ba37a92048b
    Removing intermediate container 8ba37a92048b
     ---> 1945f1d29371
    Step 6/6 : EXPOSE 80
     ---> Running in 80283a5e3fca
    Removing intermediate container 80283a5e3fca
     ---> 45e2610622e5
    Successfully built 45e2610622e5
    Successfully tagged ubuntu14.04/apache2:latest
    

    构建缓存:

    在执行构建镜像的过程中,我们发现当执行apt-get update时,返回Using cache。Docker会将之前的镜像层看做缓存,因为在安装nginx前并没有做其他的修改,因此Docker会将之前构建时创建的镜像当做缓存并作为新的开始点。然后,有些时候需要确保构建过程不会使用缓存。可以使用docker build 的 --no-cache标志。

    [root@docker wwwroot]#docker build --no-cache -t="ubuntu14.04/nginx1.4" .
    

    构建缓存带来的一个好处就是,我们可以实现简单的Dockerfile模板(比如在Dockerfile文件顶部增加包仓库或者更新包,从而尽可能确保缓存命中)。  

    # cat Dockerfile
     
    # Version: 1.0.0
    FROM ubuntu:14.04
    MAINTAINER Caoss
    ENV REFRESHED_AT 2019-04-03
    RUN apt-get update
    RUN apt-get install -y nginx
    RUN echo 'Hello, This is Nginx Container' > /usr/share/nginx/html/index.html
    EXPOSE 80
    
    • ENV 在镜像中设置环境变量,在这里设置了一个名为REFRESHED_AT的环境变量,这个环境变量用来表明该镜像模板最后的更新时间,这样只需要修改ENV指令中的日期,这使Docker在命中ENV指令时开始重置这个缓存,并运行后续指令而无需依赖该缓存。也就是说,RUN apt-get update这条指令将会被再次执行,包缓存也将会被刷新为最新内容。

    查看新镜像:

    现在来看一下新构建的镜像,使用docker images命令,如果想深入探求镜像如何构建出来的,可以使用docker history命令看到新构建的ubuntu14.04/apache2镜像的每一层,以及创建这些层的Dockerfile指令。

    [root@docker wwwroot]# docker images ubuntu14.04/nginx1.4
    REPOSITORY             TAG                 IMAGE ID            CREATED             SIZE
    ubuntu14.04/nginx1.4   latest              7403f0b39124        2 minutes ago       222MB
    [root@docker wwwroot]# docker history 7403f0b39124
    IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
    7403f0b39124        2 minutes ago       /bin/sh -c #(nop)  EXPOSE 80                    0B                  
    e2890a4ea797        2 minutes ago       /bin/sh -c echo 'Hello, This is  Nginx Conta…   32B                 
    d32c6a998439        2 minutes ago       /bin/sh -c apt-get install -y nginx             20.9MB              
    d37c74ed6540        2 minutes ago       /bin/sh -c apt-get update                       13.4MB              
    ed979c8ce28f        3 minutes ago       /bin/sh -c #(nop)  MAINTAINER Caoss             0B                  
    390582d83ead        3 weeks ago         /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B                  
    <missing>           3 weeks ago         /bin/sh -c mkdir -p /run/systemd && echo 'do…   7B                  
    <missing>           3 weeks ago         /bin/sh -c rm -rf /var/lib/apt/lists/*          0B                  
    <missing>           3 weeks ago         /bin/sh -c set -xe   && echo '#!/bin/sh' > /…   195kB               
    <missing>           3 weeks ago         /bin/sh -c #(nop) ADD file:4a03a167b06c4f47e…   188MB                 

    以新镜像启动容器

    下面我们来基于Dockerfile 来重新构建一个镜像并启动一个新的容器,检查之前的构建工作是否一切正常:

    [root@docker wwwroot]# docker build -t="ubuntu14.04/nginx1.4" .
    
    [root@docker wwwroot]# docker images ubuntu14.04/nginx1.4
    REPOSITORY             TAG                 IMAGE ID            CREATED             SIZE
    ubuntu14.04/nginx1.4   latest              7403f0b39124        2 minutes ago       222MB
    [root@docker wwwroot]# docker history 7403f0b39124 IMAGE CREATED CREATED BY SIZE COMMENT 7403f0b39124
    2 minutes ago /bin/sh -c #(nop) EXPOSE 80 0B e2890a4ea797 2 minutes ago /bin/sh -c echo 'Hello, This is Nginx Conta… 32B d32c6a998439 2 minutes ago /bin/sh -c apt-get install -y nginx 20.9MB d37c74ed6540 2 minutes ago /bin/sh -c apt-get update 13.4MB ed979c8ce28f 3 minutes ago /bin/sh -c #(nop) MAINTAINER Caoss 0B 390582d83ead 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B <missing> 3 weeks ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B <missing> 3 weeks ago /bin/sh -c rm -rf /var/lib/apt/lists/* 0B <missing> 3 weeks ago /bin/sh -c set -xe && echo '#!/bin/sh' > /… 195kB <missing> 3 weeks ago /bin/sh -c #(nop) ADD file:4a03a167b06c4f47e… 188MB [root@docker wwwroot]#
    [root@docker wwwroot]# docker run -d -p 80 --name nginx  ubuntu14.04/nginx1.4 nginx -g "daemon off";
    4d39f1a281436e3ac015ade933b33bac976db10ba107e5bfd5f7636ac813ae5f
    
    • -d选项,告诉Docker以分离(detached)的方式在后台运行。这种方式非常适合运行类似Nginx守护进程这样的需要长时间运行的进程。
    • 这里也指定了需要在容器中运行的命令:nginx -g "daemon off;"。这将以前台运行的方式启动Nginx,来作为我们的Web服务器。
    • -p选项,控制Docker在运行时应该公开哪些网络端口给外部(宿主机)。运行一个容器时,Docker可通过两种方法在宿主机上分配端口。

                * Docker可以在宿主机上通过/proc/sys/net/ipv4/ip_local_port_range文件随机一个端口映射到容器的80端口。

                * 可以在Docker宿主机中指定一个具体的端口号来映射到容器的80端口上。

     这将在Docker宿主机上随机打开一个端口,这个端口会连接到容器中的80端口上。可以使用docker ps命令查看容得的端口分配情况: 

    [root@docker wwwroot]# docker ps -l 
    CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS                   NAMES
    603dd5babee2        ubuntu14.04/nginx1.4   "nginx -g 'daemon of…"   13 seconds ago      Up 12 seconds       0.0.0.0:32772->80/tcp   nginx
    

    如果没有启动成功,则通过交互的方式进入我们新创建的镜像中,尝试启动nginx,通过分析错误日志查出不能正常启动的原因。

    我们也可以通过docker port 来查看容器的端口映射情况:

    [root@docker wwwroot]# docker port  603dd5babee2 80
    0.0.0.0:32768
    

    在上面的命令中我们指定了想要查看映射情况的容器ID和容器的端口号,这里是80。该命令返回了宿主机中映射的端口,即32768

    -p 选项还让我们可以灵活地管理容器和宿主机之间的端口映射关系。比如,可以指定将容器中的端口映射到Docker宿主机的某一个特定的端口上: 

    [root@docker wwwroot]# docker run -d -p 80:80  --name nginx  ubuntu14.04/nginx1.4   nginx -g "daemon off;"
    a97db3f2e7dbffe2809eb80c82bef0f309f3e2681eb8305774e17ece8b976b33
    [root@docker wwwroot]# docker ps -l 
    CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS                NAMES
    a97db3f2e7db        ubuntu14.04/nginx1.4   "nginx -g 'daemon of…"   5 seconds ago       Up 3 seconds        0.0.0.0:80->80/tcp   nginx
    

    上面的命令会将容器内的80端口绑定到本地宿主机的80端口上。我们也可以将端口绑定限制在特定的网络接口(即ip地址)上:

    [root@docker wwwroot]# docker run -d -p 127.0.0.1:80:80 --name nginx  ubuntu14.04/nginx1.4   nginx -g "daemon off;"
    

    我们也可以使用类似的方法将容器内的80端口绑定到一个特定网络接口的随机端口上:

    [root@docker wwwroot]# docker run -d -p 127.0.0.1::80 --name nginx  ubuntu14.04/nginx1.4  nginx -g "daemon off;"
    

    Docker还提供了一个更简单的方式,即-P参数,该参数可以用来对外公开在Dockfile中的EXPOSE指令中设置的所有端口:

    [root@docker wwwroot]# docker run -d  -P --name nginx  ubuntu14.04/nginx1.4 nginx -g "daemon off;"
    5703b1f36e96880c8294d9ba5fb8ae2bd6b0e1b7862b5ed62923d46c063ddb93
    [root@docker wwwroot]# docker ps -l 
    CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS                   NAMES
    5703b1f36e96        ubuntu14.04/nginx1.4   "nginx -g 'daemon of…"   4 seconds ago       Up 3 seconds        0.0.0.0:32769->80/tcp   nginx
    

    该命令会将容器内的80端口对本地宿主机公开,并且绑定到宿主机的一个随机端口上。该命令会将用来构建该镜像的Dockerfile文件中EXPOSE指令指定的其他端口也一并公开。

    [root@docker ~]# curl 192.168.31.44:32769
    Hello, This is  Nginx Container
    

    删除镜像

    我们可以通过docker rmi命令来删除一个镜像

    [root@docker ~]# docker ps -l 
    CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS                   NAMES
    5703b1f36e96        ubuntu14.04/nginx1.4   "nginx -g 'daemon of…"   12 minutes ago      Up 12 minutes       0.0.0.0:32769->80/tcp   nginx
    [root@docker ~]# docker stop 5703b1f36e96
    5703b1f36e96
    [root@docker ~]# docker images
    REPOSITORY             TAG                 IMAGE ID            CREATED             SIZE
    ubuntu14.04/nginx1.4   latest              7403f0b39124        About an hour ago   222MB
    ubuntu                 14.04               390582d83ead        3 weeks ago         188MB
    [root@docker ~]# docker rmi ubuntu14.04/nginx1.4
    Error response from daemon: conflict: unable to remove repository reference "ubuntu14.04/nginx1.4" (must force) - container 5703b1f36e96 is using its referenced image 7403f0b39124
    

    删除镜像ubuntu14.04/nginx1.4 ,提示无法删除引用的仓库,容器ID:5703b1f36e96 正在使用应用的镜像7403f0b39124

    此时可以加 -f 参数执行强制删除命令:

    [root@docker ~]# docker rmi -f ubuntu14.04/nginx1.4
    Untagged: ubuntu14.04/nginx1.4:latest
    Deleted: sha256:7403f0b39124fbc5d9a7e97104bf4f6dfda66afd73aebd5038d232e27eb70c9e
    Deleted: sha256:e2890a4ea797f7341547a0acfb502f9a45dc4b4feabde7580c9908b19610d81f
    Deleted: sha256:d32c6a998439360637e1877f7c3e0c26258d87b3fd3fd1ddc8e57d1f6f527947
    Deleted: sha256:d37c74ed654063018dac53cb0afb45712286148d156d3d85429d4e184883ce1f
    Deleted: sha256:ed979c8ce28ffa85da7c66093a277311f8025977177625e7405e8da3dd435491
    

    在这里可以看到Docker的分层文件系统:每个Deleted都代表一个镜像层被删除。该操作只会将本地的镜像删除。如果我们想删除本地的所有镜像可以像这样:

    [root@docker ~]# docker rmi `docker images -a -q`

      

     本文参考链接:https://www.cnblogs.com/Bourbon-tian/p/6867796.html

      

    千里之行始于笔下
  • 相关阅读:
    HDU 1358 Period (KMP)
    POJ 1042 Gone Fishing
    Csharp,Javascript 获取显示器的大小的几种方式
    css text 自动换行的实现方法 Internet Explorer,Firefox,Opera,Safar
    Dynamic Fonts动态设置字体大小存入Cookie
    CSS Image Rollovers翻转效果Image Sprites图片精灵
    CSS three column layout
    css 自定义字体 Internet Explorer,Firefox,Opera,Safari
    颜色选择器 Color Picker,Internet Explorer,Firefox,Opera,Safar
    CSS TextShadow in Safari, Opera, Firefox and more
  • 原文地址:https://www.cnblogs.com/caoshousong/p/10536042.html
Copyright © 2011-2022 走看看