构建镜像docker bulid
简介
镜像构建的完整命令为docker build [OPTIONS] PATH | URL | -
。在这条语句中,并没有指定Dockerfile路径,这是因为大家都习惯使用默认的文件名,实际上 Dockerfile
的文件名并不要求必须为 Dockerfile
,而且并不要求必须位于上下文目录中,比如可以用 -f ../Dockerfile.php
参数指定某个文件作为 Dockerfile
。
PATH指的是上下文目录,那么什么是上下文呢?首先我们要理解 docker build
的工作原理。Docker 在运行时分为 Docker daemon(也就是服务端守护进程)和docker client客户端工具。Docker 的引擎提供了一组 REST API,被称为 Docker Remote API,而如 docker
命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种 docker
功能,但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成。也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。
docker build
命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。所以才引入了上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径,docker build
命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。
所以在一般情况下,我们会在构建镜像的时候,会先创建一个空目录,然后把所需要的文件都放置在这个目录下,包括Dockerfile文件,需要构建时,使用docker bulid -t name:verison .
来运行。而 .
表示PATH,即上下文,docker client会把这个目录里面的文件都打包,传给docker daemon。正是由于这个特性,所有Dockerfile的指令中,COPY、ADD这两个都只能使用相对绝对,即COPY ./a.txt /test/
实例
使用Dockerfile来构建一个tank大战的image。先下载源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[root@master ~]# git clone https://codehub.devcloud.huaweicloud.com/Demo04260/tank.git Cloning into 'tank'... remote: Enumerating objects: 90, done. remote: Counting objects: 100% (90/90), done. remote: Compressing objects: 100% (86/86), done. remote: Total 90 (delta 9), reused 0 (delta 0) Unpacking objects: 100% (90/90), done. [root@master ~]# cd tank/ [root@master tank]# cat Dockerfile FROM nginx LABEL maintainer "fangdm<8@8994.cn>" EXPOSE 80 COPY . /usr/share/nginx/html |
开始创建,运行命令之后,从第一个输出内容可以看到,docker有包内容全部打包给了Docker daemon。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
[root@master tank]# docker build -t tank . Sending build context to Docker daemon 2.315MB Step 1/4 : FROM nginx 62c261073ecf Step 2/4 : LABEL maintainer "fangdm<8@8994.cn>" Running in 33da0adbde73 Removing intermediate container 33da0adbde73 ca6c7c4571b7 Step 3/4 : EXPOSE 80 Running in 74879a5d980b Removing intermediate container 74879a5d980b d0649d7b613d Step 4/4 : COPY . /usr/share/nginx/html 46a940d07f6d Successfully built 46a940d07f6d Successfully tagged tank:latest [root@master tank]# docker images tank REPOSITORY TAG IMAGE ID CREATED SIZE tank latest 46a940d07f6d 3 minutes ago 111MB |
这样就创建成功了。运行:
1 2 3 4 |
[root@master tank]# docker run --name tank -d -P tank 11c8314f6f5db62fe0783b52c6fccb45b36ed0539c192076be3f56bf7605fd0b [root@master tank]# docker port tank 80/tcp -> 0.0.0.0:32769 |
访问docker服务器的32769端口即可开始坦克大战了。
由于在构建的过程中docker会采用缓存的机制,如果需要重新构建,不想使用cache需要添加—no-cache。
Dockerfile指令
Dockerfile是一个包含用户能够构建镜像的所有命令的文本文档,它有自己的语法以及命令,docker能够从dockerfile中读取指令自动的构建镜像。
FROM
FROM
就是指定 基础镜像,因此一个 Dockerfile
中 FROM
是必备的指令,并且必须是第一条指令。有以下格式:
1 2 3 4 |
FROM scratch FROM <image> [AS <name>] FROM <image>[:<tag>] [AS <name>] FROM <image>[@<digest>] [AS <name>] |
scratch
这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。以 scratch
为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。需要自己来一层一层地创建。直接 FROM scratch
会让镜像体积更加小巧。使用 Go 语言 开发的应用很多会使用这种方式来制作镜像,这也是为什么有人认为 Go 是特别适合容器微服务架构的语言的原因之一。
如果没有指定 tag ,latest 将会被指定为要使用的基础镜像版本。
允许多个from,即就是多阶段构建。后续介绍
MAINTAINER
格式为MAINTAINER user_name user_email,指定维护者信息。可以使用LABEL代替。
LABEL
给镜像添加元数据,可以用LABEL命令替换MAINTAINER命令。指定一些作者、邮箱等信息。设置之后,使用docker inspect来查看。
具体格式:LABEL <key>=<value> <key>=<value> <key>=<value> ...
1 2 |
[root@master tank]# docker inspect -f '{{.Config.Labels}}' tank map[maintainer:fangdm<8@8994.cn>] |
RUN
格式为:RUN ["executable", "param1", "param2"]
或者 RUN <command>
RUN会在当前镜像的最上面创建一个新层,并且能执行任何的命令,然后对执行的结果进行提交。提交后的结果镜像在dockerfile的后续步骤中可以使用。
如果有多个 RUN 指令,最佳建议是使用 && 合并为一个,并且格式要统一。
1 2 3 4 5 6 |
FROM php:7.1-fpm RUN apt-get update && apt-get install -y libfreetype6-dev libjpeg62-turbo-dev libmcrypt-dev libpng-dev \ && docker-php-ext-install -j$(nproc) iconv mcrypt mysqli pdo_mysql \ && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \ && docker-php-ext-install -j$(nproc) gd |
WORKDIR
格式为: WORKDIR /path/to/workdir
WORKDIR 用来指定工作目录的。Docker 默认的工作目录是/,只有 RUN 能执行 cd 命令切换目录,而且还只作用在当下下的 RUN,也就是说每一个 RUN 都是独立进行的。如果想让其他指令在指定的目录下执行,就得靠 WORKDIR。WORKDIR 动作的目录改变是持久的,不用每个指令前都使用一次 WORKDIR。如果该目录不存在,则会创建。
ENV
设置环境变量,设置的变量可供后面指令使用。可以使用docker inspect查的。使用docker exec进入容器之后,使用env命令也是可以输出的。
设置的方法是: ENV <key1>=<value1> <key2>=<value2>...
或者 ENV <key> <value>
,建议还加上等号,比较直观。
如果在容器运行时想修改环境变量的值 ,可以使用docker run --env <key>=<value>
来修改环境变量。
1 2 3 4 5 6 7 |
[root@master tank]# docker exec tank env PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin HOSTNAME=11c8314f6f5d NGINX_VERSION=1.17.0 NJS_VERSION=0.3.2 PKG_RELEASE=1~stretch HOME=/root |
ARG
格式:ARG <参数名>[=<默认值>]
构建参数和 ENV
的效果一样,都是设置环境变量。所不同的是,ARG
所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。即ARG 定义的变量只在建立 image 时有效,建立完成后变量就失效消失
。
Dockerfile
中的 ARG
指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令 docker build
中用 --build-arg <参数名>=<值>
来覆盖。
使用时,类似shell的变量一样,前面加$
即可。
COPY
格式:COPY [--chown=<user>:<group>] <源路径>... <目标路径>
或者 COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]
COPY 将文件从路径 复制添加到容器内部路径 。 必须是想对于源文件夹的一个文件或目录,也可以是一个远程的url, 是目标容器中的绝对路径。所有的新文件和文件夹都会创建UID 和 GID 。事实上如果 是一个远程文件URL,那么目标文件的权限将会是600。用法是:COPY [--chown=<user>:<group>] <源路径>... <目标路径>
标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。用 COPY
指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。在使用该指令的时候还可以加上 --chown=<user>:<group>
选项来改变文件的所属用户及所属组。
ADD
跟COPY的作用一样,都是复制文件到容器。但比COPY更高级,会自动下载或者自动解压,如果 <源路径> 为一个 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,ADD 指令将会自动解压缩这个压缩文件到 <目标路径> 去。
VOLUME
定义数据卷,格式为:VOLUME ["<路径1>", "<路径2>"...]
或者 VOLUME <路径>
定义完成之后,docker在创建容器时,会自动创建volume数据卷,这时即使容器删除了,其数据也不是丢失。
EXPOSE
指定端口。格式为 EXPOSE <端口1> [<端口2>...]
。
指定容器运行时对外暴露的端口,但是该指定实际上不会发布该端口,它的功能是镜像构建者和容器运行者之间的记录文件。run命令有-p和-P两个参数,如果是-P(大写)就是会随机端口映射,容器内会随机映射到EXPOSE指定的端口。
CMD
格式:
- shell 格式:CMD <命令>
- exec 格式:CMD [“可执行文件”, “参数1”, “参数2”…]
在指令格式上,一般推荐使用 exec
格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 "
,而不要使用单引号。注意,如果使用 shell
格式的话,实际的命令会被包装为 sh -c
的参数的形式进行执行。即如果CMD echo $HOME
,在实际执行中,会将其变更为:CMD [ "sh", "-c", "echo $HOME" ]
指定容器启动时默认运行的命令,在一个Dockerfile文件中,如果有多个CMD命令,只有一个最后一个会生效。RUN指令是在构建镜像时候执行的,而CMD指令是在每次容器运行的时候执行的。
而如果docker run命令接了command,会覆盖CMD的命令。
对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西。如果写为 CMD service nginx start
,会解析为 CMD [ "sh", "-c", "service nginx start"]
,这样主进程是sh了。那么当 service nginx start
命令结束后,sh
也就结束了,sh
作为主进程退出了,自然就会令容器退出。正确的做法是直接执行 nginx
可执行文件,并且要求以前台形式运行。比如:CMD ["nginx", "-g", "daemon off;"]
ENTRYPOINT
ENTRYPOINT
的格式和 RUN
指令格式一样,分为 exec
格式和 shell
格式。
ENTRYPOINT
的目的和 CMD
一样,都是在指定容器启动程序及参数。ENTRYPOINT
在运行时也可以替代,不过比 CMD
要略显繁琐,需要通过 docker run
的参数 --entrypoint
来指定。
ENTRYPOINT和CMD同时存在时, docker把CMD的命令拼接到ENTRYPOINT命令之后,即<ENTRYPOINT> "<CMD>"
, 拼接后的命令才是最终执行的命令。
在一个Dockerfile文件中,如果有多个ENTRYPOINT命令,也只有一个最后一个会生效。而不同点是通过docker run command
命令会覆盖CMD的命令,但如果有ENTRYPOINT,则执行的命令不会覆盖ENTRYPOINT,docker run命令中指定的任何参数都会被当做参数传递给ENTRYPOINT。
例如,如果Dockerfile只有 CMD [ "curl", "-s", "https://ip.cn" ]
,那么运行 docker run myip -i
会出错,如果是 ENTRYPOINT [ "curl", "-s", "https://ip.cn" ]
,运行 docker run myip -i
就会输出Http头部信息,这是因为当存在 ENTRYPOINT
后,CMD
的内容将会作为参数传给 ENTRYPOINT
,而这里 -i
就是新的 CMD
,因此会作为参数传给 curl
,从而达到了我们预期的效果。
有关这两者的区别,可以参考:https://yeasy.gitbooks.io/docker_practice/content/image/dockerfile/entrypoint.html 以及 Dockerfile: ENTRYPOINT和CMD的区别
HEALTHCHECK
格式:
HEALTHCHECK [选项] CMD <命令>
:设置检查容器健康状况的命令HEALTHCHECK NONE
:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
HEALTHCHECK
指令是告诉 Docker 应该如何进行判断容器的状态是否正常,这是 Docker 1.12 引入的新指令。支持下列选项:
--interval=<间隔>
:两次健康检查的间隔,默认为 30 秒;--timeout=<时长>
:健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;--retries=<次数>
:当连续失败指定次数后,则将容器状态视为unhealthy
,默认 3 次。
如下的Dockerfile
1 2 3 4 |
FROM nginx RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* HEALTHCHECK --interval=5s --timeout=3s \ CMD curl -fs http://localhost/ || exit 1 |
运行之后就会检测其健康,如下是正常的,healthy:
1 2 3 |
root@fdm:~/test# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 6cd513fdb549 myweb:v1 "nginx -g 'daemon ..." 19 minutes ago Up 19 minutes (healthy) 80/tcp sleepy_booth |
.dockerignore
这个不是指令,只是一个文件,用来忽略上下文目录中包含的一些image用不到的文件,它们不会传送到docker daemon。
其他的一些指令,可以参考:
实例
ubuntu ssh
官方镜像都没有ssh的功能,给他加上吧。先创建一个空的目录,然后创建以下文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
root@fdm:~/sshd_ubuntu# cat Dockerfile FROM ubuntu:18.04 MAINTAINER fangdm <5608381@qq.com> ADD run.sh / RUN apt-get update && apt-get install -y openssh-server && \ mkdir -p /var/run/sshd && \ echo 'root:tjvIa1fp' | chpasswd && \ echo "PermitRootLogin yes" >>/etc/ssh/sshd_config && \ sed -i 's/#PasswordAuthentication/PasswordAuthentication/' /etc/ssh/sshd_config && \ echo 'tjvIa1fp' >/root/passwd.txt && \ chmod 755 /run.sh && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* EXPOSE 22 CMD ["/run.sh"] root@fdm:~/sshd_ubuntu# cat run.sh !/bin/bash echo "root password: tjvIa1fp" /usr/sbin/sshd -D |
然后直接docker build -t ubuntu-sshd:v1 .
即可。完成之后,可以看到,其大小比原来的ubuntu大了三分之二。仅仅只是安装了openssh-server。
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 |
[root@master ubuntu_ssh]# docker images |grep ubuntu ubuntu-sshd v1 d7c322e5bcf5 10 minutes ago 179MB ubuntu 18.04 2ca708c1c9cc 3 days ago 64.2MB ubuntu latest a2a15febcdf3 5 weeks ago 64.2MB [root@master ubuntu_ssh]# docker run -itd -P --name ssh ubuntu-sshd:v1 fba8152f891cad00ef2e5aeb4cdeede5d17e59b0182e5ea049bb2fae6f8f2732 [root@master ubuntu_ssh]# docker ps -l CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES fba8152f891c ubuntu-sshd:v1 "/run.sh" 10 seconds ago Up 6 seconds 0.0.0.0:32771->22/tcp ssh 查密码 [root@master ubuntu_ssh]# docker logs ssh root password: tjvIa1fp 查IP [root@master ubuntu_ssh]# docker inspect -f '{{ .NetworkSettings.IPAddress }}' ssh 172.17.0.4 登陆 [root@master ubuntu_ssh]# ssh 172.17.0.4 The authenticity of host '172.17.0.4 (172.17.0.4)' can't be established. ECDSA key fingerprint is SHA256:bS6WcqPKrrbhgAJ9LgRdabc5kKbtRMH1Hgykz71tatY. ECDSA key fingerprint is MD5:a5:3d:88:2f:2b:c7:a0:78:0e:c1:95:51:1d:72:4d:2b. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added '172.17.0.4' (ECDSA) to the list of known hosts. root@172.17.0.4's password: root@fba8152f891c:~# cat /etc/issue Ubuntu 18.04.3 LTS \n \l root@fba8152f891c:~# |
在此基本上,可以再次制作nginx,不过是简单版:
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 |
root@fdm:~# cat nginx/Dockerfile FROM ubuntu-sshd:v1 LABEL maintainer="fangdm <8@8994.cn>" ADD run.sh /run.sh WORKDIR /etc/nginx RUN apt-get install -y nginx && \ rm -rf /var/lib/apt/lists/* && \ echo "daemon off;" >>/etc/nginx/nginx.conf && \ chown -R www-data:www-data /var/lib/nginx && \ rm -f /usr/share/nginx/html/index.html && \ echo "Asia/Shanghai" > /etc/timezone && \ #dpkg-reconfigure -f noninteractive tzdata && \ chmod 755 /run.sh COPY index.html /var/www/html VOLUME ["/etc/nginx/sites-enabled","/etc/nginx/certs","/etc/nginx/conf.d","/var/log/nginx"] CMD ["/run.sh"] EXPOSE 80 EXPOSE 443 root@fdm:~# cat nginx/run.sh !/bin/bash /usr/sbin/sshd & /usr/sbin/nginx |
可以使用supervisord来控制服务,https://my.oschina.net/renguijiayi/blog/365087
nginx-alpine版
alpine介绍
首先先介绍下busybox以及alpine。
- busyBox是一个集成了一百多个最常用Linux命令和工具(如cat、echo、grep、mount、telnet等)的精简工具箱,它只有2MB的左右的大小,很方便进行各种快速验证,被誉为“Linux系统的瑞士军刀”。BusyBox可运行于多款POSIX环境的操作系统中,如Linux(包括Android)、Hurd、FreeBSD等。官网是:https://busybox.net/
- Alpine Linux 是一个社区开发的面向安全应用的轻量级Linux发行版。 Alpine 的意思是“高山的”,它采用了musl libc和busybox以减小系统的体积和运行时资源消耗,但功能上比BusyBox又完善得多。在保持瘦身的同时,Alpine还提供了自己的包管理工具apk,可以通过https://pkgs.alpinelinux.org/packages查询包信息,也可以通过apk命令直接查询和安装各种软件。官网是:https://www.alpinelinux.org/
两者的大小为:
1 2 3 |
[root@master ~]# docker images |egrep 'busybox|alpine' busybox latest 19485c79a9bb 2 weeks ago 1.22MB alpine latest 961769676411 4 weeks ago 5.58MB |
由于alpine小巧、安全、简单以及功能完备的特点,被广泛应用于众多Docker容器中。不同于centos使用yum、ubuntu使用apt-get去安装软件,alpine使用了apk这个指令去安装相应的指令。以下是修改apk使用阿里的源。
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 |
alpine 只有/bin/sh,没有/bin/bash [root@master ~]# docker exec -it alpine /bin/bash OCI runtime exec failed: exec failed: container_linux.go:345: starting container process caused "exec: \"/bin/bash\": stat /bin/bash: no such file or directory": unknown 修改源 [root@master ~]# docker exec -it alpine /bin/sh / # cat /etc/apk/repositories http://dl-cdn.alpinelinux.org/alpine/v3.10/main http://dl-cdn.alpinelinux.org/alpine/v3.10/community / # echo -e "http://mirrors.aliyun.com/alpine/latest-stable/main\nhttp://mirrors.aliyun.com/alpine/latest-stable/community" >/etc/apk/repositories / # cat /etc/apk/repositories http://mirrors.aliyun.com/alpine/latest-stable/main http://mirrors.aliyun.com/alpine/latest-stable/community / # vim /etc/apk/repositories /bin/sh: vim: not found 安装 vim 命令 / # apk add vim fetch http://mirrors.aliyun.com/alpine/latest-stable/main/x86_64/APKINDEX.tar.gz fetch http://mirrors.aliyun.com/alpine/latest-stable/community/x86_64/APKINDEX.tar.gz (1/5) Installing lua5.3-libs (5.3.5-r2) (2/5) Installing ncurses-terminfo-base (6.1_p20190518-r0) (3/5) Installing ncurses-terminfo (6.1_p20190518-r0) (4/5) Installing ncurses-libs (6.1_p20190518-r0) (5/5) Installing vim (8.1.1365-r0) Executing busybox-1.30.1-r2.trigger OK: 40 MiB in 19 packages / # exit |
使用apk --update add --no-cache <package>
来安装命令。安装完成vim,就增加了40MB了。
根据官方的alpine Dockerfile来看,其实非常简单:
1 2 3 |
FROM scratch ADD alpine-minirootfs-3.10.2-x86_64.tar.gz / CMD ["/bin/sh"] |
构建nginx
首先先看一下官方的写法 docker-nginx Dockerfile ,可以看出,其思路是直接nginx提供的源,然后直接使用apk去安装。这是比较方便,但是无法自定义参数,所以还是需要使用编译大法。
先下载必要的软件包:
1 2 3 |
git clone https://github.com/alibaba/nginx-http-concat.git git clone https://github.com/FRiCKLE/ngx_cache_purge.git wget https://nginx.org/download/nginx-1.16.1.tar.gz |
编写Dockerfile,nginx默认安装在/usr/local/nginx目录下,如下:
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 |
[root@master nginx]# cat Dockerfile FROM alpine:3.10 as builder LABEL maintainer="fangdm<8@8994.cn>" ENV NGINX_VERSION 1.16.1 ENV CONFIG "--prefix=/usr/local/nginx --user=www --group=www --with-http_auth_request_module --with-http_realip_module --with-http_v2_module --with-debug --with-http_random_index_module --with-http_sub_module --with-http_addition_module --with-http_secure_link_module --with-http_ssl_module --with-stream_ssl_module --with-stream_realip_module --with-stream_ssl_preread_module --with-stream --add-module=../ngx_cache_purge --add-module=../nginx-http-concat --with-http_slice_module --with-threads --with-http_gzip_static_module --with-http_gunzip_module --with-http_stub_status_module" ADD . /nginx/ RUN set -x \ && addgroup -g 101 -S www \ && adduser -S -D -H -u 101 -h /var/cache/www -s /sbin/nologin -G www -g www www \ && echo -e "http://mirrors.aliyun.com/alpine/latest-stable/main\nhttp://mirrors.aliyun.com/alpine/latest-stable/community" >/etc/apk/repositories \ && apk add --no-cache --virtual .bulidDeps gcc g++ make libxslt-dev libxml2-dev gd-dev wget linux-headers pcre pcre-dev zlib zlib-dev openssl openssl-dev geoip geoip-dev \ && cd /nginx \ && tar zxf nginx-1.16.1.tar.gz \ && cd nginx-1.16.1 \ && ./configure $CONFIG \ && make \ && make install \ && cd / \ && tar zcvf nginx.tar.gz /usr/local/nginx FROM alpine:3.10 ENV NGINX_VERSION="1.16.1" LANG="en_US.UTF-8" GLIBC_PKG_VERSION="2.30-r0" COPY --from=builder /nginx.tar.gz / RUN set -x \ && cd / \ && tar zxvf /nginx.tar.gz \ && addgroup -g 101 -S www \ && adduser -S -D -H -u 101 -h /var/cache/www -s /sbin/nologin -G www -g www www \ && echo -e "http://mirrors.aliyun.com/alpine/latest-stable/main\nhttp://mirrors.aliyun.com/alpine/latest-stable/community" >/etc/apk/repositories \ && apk add --no-cache --virtual .nginx-rundeps gettext gd libxslt geoip pcre\ && mv /usr/bin/envsubst /tmp/ \ && runDeps="$( \ scanelf --needed --nobanner /tmp/envsubst \ | awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' \ | sort -u \ | xargs -r apk info --installed \ | sort -u \ )" \ && apk add --no-cache $runDeps \ && mv /tmp/envsubst /usr/local/bin/ \ && apk add --no-cache tzdata \ && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && echo "Asia/Shanghai" > /etc/timezone \ && ln -sf /dev/stdout /usr/local/nginx/logs/access.log \ && ln -sf /dev/stderr /usr/local/nginx/logs/error.log \ \ #安装中文字体库 && cd /usr/local/src \ && curl -Lo /etc/apk/keys/sgerrand.rsa.pub "https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_PKG_VERSION}/sgerrand.rsa.pub" \ && curl -Lo glibc-${GLIBC_PKG_VERSION}.apk "https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_PKG_VERSION}/glibc-${GLIBC_PKG_VERSION}.apk" \ && curl -Lo glibc-bin-${GLIBC_PKG_VERSION}.apk "https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_PKG_VERSION}/glibc-bin-${GLIBC_PKG_VERSION}.apk" \ && curl -Lo glibc-i18n-${GLIBC_PKG_VERSION}.apk "https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_PKG_VERSION}/glibc-i18n-${GLIBC_PKG_VERSION}.apk" \ && apk add glibc-${GLIBC_PKG_VERSION}.apk glibc-bin-${GLIBC_PKG_VERSION}.apk glibc-i18n-${GLIBC_PKG_VERSION}.apk \ && /usr/glibc-compat/bin/localedef --force --inputfile POSIX --charmap UTF-8 "$LANG" || true \ && echo "export LANG=$LANG" > /etc/profile.d/locale.sh \ && rm -fr /usr/local/src /var/cache/apk/* EXPOSE 80 CMD ["/usr/local/nginx/sbin/nginx", "-g", "daemon off;"] |
为了减少镜像的体积,使用了多阶段构建的方法。同时多阶段构建时,只能复制文件,不能复制目录,所以需要对/usr/local/nginx进行打包,使用“docker build -t nginx_alpine .构建完成之后,可以看到nginx_alpine大小为31.1M,而编译过程中,产生了284M。
1 2 3 4 |
[root@master nginx]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE nginx_alpine latest 7c74ceae2589 24 minutes ago 31.1MB <none> <none> bc55a29db5af 25 minutes ago 284MB |
另外,alpine是不支持中文,要集成中文环境,可以参考:https://www.clxz.top/2019/05/09/160241/
创建centos镜像
tar打包构建
第一种方法,通过tar打包构建基础镜像:
1 2 3 |
yum clean all rm -rf /var/cache/yum tar --numeric-owner --exclude=/proc --exclude=/sys --exclude=/mnt --exclude=/var/cache --exclude=/usr/share/{foomatic,backgrounds,perl5,fonts,cups,qt4,groff,kde4,icons,pixmaps,emacs,gnome-background-properties,sounds,gnome,games,desktop-directories} --exclude=/var/log -zcvf /mnt/CentOS-7.6-BaseImage.tar.gz / |
安装docker:
1 2 3 4 5 6 7 |
安装EPEL源和REMI源 rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm rpm -Uvh https://rpms.remirepo.net/enterprise/remi-release-7.rpm 安装Docker软件包 yum install -y docker-io 启动Docker服务 systemctl start docker.service |
将打包好的文件导入到docker中,然后验证:
1 2 3 4 5 6 7 8 9 |
[root@localhost ~]# cat /mnt/CentOS-7.-BaseImage.tar.gz |docker import - centos-tar:7.6 sha256:b629e1dd6b5365d96cc8aa0d4918215a1609fd2d8d41688a3507c78f428451d9 [root@localhost ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE centos-tar 7.6 b629e1dd6b53 2 minutes ago 751 MB [root@localhost ~]# docker run --rm -it centos-tar:7.6 bash [root@949f13b77435 /]# w 09:11:52 up 1:12, 2 users, load average: 0.12, 0.62, 0.42 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT |
mkimage-yum.sh脚本构建
第二种方法,运行mkimage-yum.sh脚本,创建CentOS的基础镜像,首先下载
wget https://raw.githubusercontent.com/moby/moby/master/contrib/mkimage-yum.sh
,然后运行脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
[root@localhost ~]# sh mkimage-yum.sh centos-mkimage + mkdir -m 755 /tmp/mkimage-yum.sh.sB2M3f/dev + mknod -m 600 /tmp/mkimage-yum.sh.sB2M3f/dev/console c 5 1 + mknod -m 600 /tmp/mkimage-yum.sh.sB2M3f/dev/initctl p + mknod -m 666 /tmp/mkimage-yum.sh.sB2M3f/dev/full c 1 7 ... + version= + for file in '"$target"/etc/{redhat,system}-release' + '[' -r /tmp/mkimage-yum.sh.7yJbJ7/etc/redhat-release ']' ++ sed 's/^[^0-9\]*\([0-9.]\+\).*$/\1/' /tmp/mkimage-yum.sh.7yJbJ7/etc/redhat-release + version=7.6.1810 + break + '[' -z 7.6.1810 ']' + tar --numeric-owner -c -C /tmp/mkimage-yum.sh.7yJbJ7 . + docker import - centos-mkimage:7.6.1810 sha256:e3156476a6818c2f12453da3b6f182a38cb70a055b75e153b9dcee36c1adbd68 + docker run -i -t --rm centos-mkimage:7.6.1810 /bin/bash -c 'echo success' success + rm -rf /tmp/mkimage-yum.sh.7yJbJ7 [root@localhost ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE centos-mkimage 7.6.1810 e3156476a681 49 seconds ago 281 MB centos-tar 7.6 b629e1dd6b53 13 minutes ago 751 MB |
由上述两图可知,通过tar
打包创建的基础镜像的体积大约是通过mkimage-yum.sh
脚本创建的基础镜像的3倍左右。这是由于前者是基于最小化安装的CentOS系统而创建的,而后者是从CentOS官方源安装必要的软件包而创建的,前者比后者多安装了很多软件包,包括邮件工具、设备驱动程序,等等
http://ghoulich.xninja.org/2017/10/13/how-to-create-docker-base-image-of-centos/
mysql 5.7
基础知识
想对mysql进行镜像化,必须掌握mysql的一些操作命令。
my_print_defaults为查询my.cnf配置文件的命令,第一个参数为section
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
[root@ee1995b6bc44 /]# my_print_defaults mysqld --port=3306 --datadir=/var/lib/mysql --max_connections=200 --character-set-server=utf8 --bind-address=0.0.0.0 --socket=/var/sock/mysqld/mysqld.sock --pid-file=/var/run/mysqld/mysqld.pid --log-error=/var/log/mysql/error.log --slow_query_log_file=/var/log/mysql/slow.log --general_log_file=/var/log/mysql/query.log [root@ee1995b6bc44 /]# [root@ee1995b6bc44 /]# [root@ee1995b6bc44 /]# [root@ee1995b6bc44 /]# my_print_defaults mysqld |grep "^--datadir=" | cut -d= -f2- | tail -n 1 /var/lib/mysql |
另外,也可以使用mysqld —verbose —help方法来获取
1 2 3 4 |
[root@xmxyk bin]#./mysqld --verbose --help 2>/dev/null | awk -v key="datadir" '$1 == key { print $2; exit }' /usr/local/mysql/var/ [root@xmxyk bin]#./mysqld --verbose --help 2>/dev/null | awk -v key="socket" '$1 == key { print $2; exit }' /tmp/mysql.sock |
以空密码的方式初始化数据库:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
[root@ee1995b6bc44 /]# mysqld --initialize-insecure --datadir="/var/lib/mysql/" --user=${MYSQL_USER} [root@ee1995b6bc44 /]# ll /var/lib/mysql total 110620 -rw-r----- 1 mysql mysql 56 Mar 16 12:35 auto.cnf -rw-r----- 1 mysql mysql 419 Mar 16 12:35 ib_buffer_pool -rw-r----- 1 mysql mysql 50331648 Mar 16 12:35 ib_logfile0 -rw-r----- 1 mysql mysql 50331648 Mar 16 12:35 ib_logfile1 -rw-r----- 1 mysql mysql 12582912 Mar 16 12:35 ibdata1 drwxr-x--- 2 mysql mysql 4096 Mar 16 12:35 mysql drwxr-x--- 2 mysql mysql 4096 Mar 16 12:35 performance_schema drwxr-x--- 2 mysql mysql 12288 Mar 16 12:35 sys [root@ee1995b6bc44 /]# cat /var/log/mysql/error.log 2019-03-16T12:35:48.881747Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details). 2019-03-16T12:35:49.345885Z 0 [Warning] InnoDB: New log files created, LSN=45790 2019-03-16T12:35:49.439030Z 0 [Warning] InnoDB: Creating foreign key constraint system tables. 2019-03-16T12:35:49.521977Z 0 [Warning] No existing UUID has been found, so we assume that this is the first time that this server has been started. Generating a new UUID: 077e6497-47e8-11e9-9508-0242ac110002. 2019-03-16T12:35:49.524840Z 0 [Warning] Gtid table is not ready to be used. Table 'mysql.gtid_executed' cannot be opened. 2019-03-16T12:35:49.526731Z 1 [Warning] root@localhost is created with an empty password ! Please consider switching off the --initialize-insecure option. |
Dockerfile
从Dockerfile文件可以看到是使用了yum进行安装的mysql 5.7,安装好了之后,生成了/etc/my.cnf文件,而启动mysql则是交给了docker-entrypoint.sh
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 |
[root@host mysql]# cat Dockerfile FROM centos:7 MAINTAINER fangdm <fang2000@vip.qq.com> LABEL image="mysql-5.7" vendor=fangdm build-date="2019-03-15" ENV REPO_URL="https://repo.mysql.com//mysql57-community-release-el7-11.noarch.rpm" \ MYSQL_ROOT_PASSWORD="**Random**" \ MYSQL_VERSION=5.7 \ MYSQL_USER=mysql \ MYSQL_GROUP=mysql \ MYSQL_UID=27 \ MYSQL_GID=27 COPY docker-entrypoint.sh / RUN groupadd -g ${MYSQL_GID} -r ${MYSQL_GROUP} && chmod 755 /docker-entrypoint.sh && \ adduser ${MYSQL_USER} -u ${MYSQL_UID} -s /sbin/nologin -M -g ${MYSQL_GROUP} RUN yum install -y epel-release && \ rpm -ivh ${REPO_URL} && \ yum-config-manager --disable mysql55-community && \ yum-config-manager --disable mysql56-community && \ yum-config-manager --disable mysql80-community && \ yum-config-manager --enable mysql57-community && \ yum install -y mysql-community-server && \ yum -y autoremove && \ yum clean all ENV MYSQL_BASE_INCL="/etc/my.cnf.d" \ MYSQL_CUST_INCL1="/etc/mysql/conf.d" \ MYSQL_MY_CNF="/etc/my.cnf" \ MYSQL_CUST_INCL2="/etc/mysql/docker-default.d" \ MYSQL_DEF_DATA="/var/lib/mysql" \ MYSQL_DEF_PID="/var/run/mysqld" \ MYSQL_DEF_SOCK="/var/sock/mysqld" \ MYSQL_DEF_LOG="/var/log/mysql" ENV MYSQL_LOG_SLOW="${MYSQL_DEF_LOG}/slow.log" \ MYSQL_LOG_ERROR="${MYSQL_DEF_LOG}/error.log" \ MYSQL_LOG_QUERY="${MYSQL_DEF_LOG}/query.log" RUN rm -rf $MYSQL_BASE_INCL $MYSQL_CUST_INCL1 $MYSQL_CUST_INCL2 $MYSQL_DEF_DATA $MYSQL_DEF_SOCK $MYSQL_DEF_LOG && \ mkdir -p $MYSQL_BASE_INCL $MYSQL_CUST_INCL1 $MYSQL_CUST_INCL2 $MYSQL_DEF_DATA $MYSQL_DEF_SOCK $MYSQL_DEF_LOG && \ chmod 0755 $MYSQL_BASE_INCL $MYSQL_CUST_INCL1 $MYSQL_CUST_INCL2 $MYSQL_DEF_DATA $MYSQL_DEF_SOCK $MYSQL_DEF_LOG && \ chown ${MYSQL_USER}:${MYSQL_GROUP} $MYSQL_BASE_INCL $MYSQL_CUST_INCL1 $MYSQL_CUST_INCL2 $MYSQL_DEF_DATA $MYSQL_DEF_SOCK $MYSQL_DEF_LOG RUN echo "[client]" >$MYSQL_MY_CNF && \ echo "socket = ${MYSQL_DEF_SOCK}/mysqld.sock" >>$MYSQL_MY_CNF && \ echo -e "default-character-set=utf8\n" >>$MYSQL_MY_CNF && \ echo "[mysql]" >>$MYSQL_MY_CNF && \ echo "socket = ${MYSQL_DEF_SOCK}/mysqld.sock" >>$MYSQL_MY_CNF && \ echo -e "default-character-set=utf8\n" >>$MYSQL_MY_CNF && \ echo "[mysqld]" >>$MYSQL_MY_CNF && \ echo "skip-name-resolve" >>$MYSQL_MY_CNF && \ echo "skip-host-cache" >>$MYSQL_MY_CNF && \ echo "port = 3306" >>$MYSQL_MY_CNF && \ echo "user = ${MYSQL_USER}" >>$MYSQL_MY_CNF && \ echo "datadir=$MYSQL_DEF_DATA" >>$MYSQL_MY_CNF && \ echo "max_connections = 200" >>$MYSQL_MY_CNF && \ echo "character-set-server = utf8" >>$MYSQL_MY_CNF && \ echo "bind-address = 0.0.0.0" >>$MYSQL_MY_CNF && \ echo "socket = ${MYSQL_DEF_SOCK}/mysqld.sock" >>$MYSQL_MY_CNF && \ echo "pid-file = ${MYSQL_DEF_PID}/mysqld.pid" >>$MYSQL_MY_CNF && \ echo "log-error = ${MYSQL_LOG_ERROR}" >>$MYSQL_MY_CNF && \ echo "general_log_file = ${MYSQL_LOG_QUERY}" >>$MYSQL_MY_CNF && \ echo "slow_query_log_file = ${MYSQL_LOG_SLOW}" >>$MYSQL_MY_CNF && \ echo "!includedir ${MYSQL_BASE_INCL}/" >>$MYSQL_MY_CNF && \ echo "!includedir ${MYSQL_CUST_INCL1}/" >>$MYSQL_MY_CNF && \ echo "!includedir ${MYSQL_CUST_INCL2}/" >>$MYSQL_MY_CNF EXPOSE 3306 VOLUME ["$MYSQL_DEF_DATA","$MYSQL_DEF_LOG","$MYSQL_CUST_INCL1","$MYSQL_CUST_INCL2"] ENTRYPOINT ["/docker-entrypoint.sh"] |
docker-entrypoint.sh
docker-entrypoint.sh内容如下,主要作用是启动Mysql,且进行初始化。此脚本由国外大神cytopia制作,实现了shell的debug模式。值得认真学习。
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 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
[root@host mysql]# cat docker-entrypoint.sh !/bin/bash run() { _cmd="${1}" _debug="0" _red="\033[0;31m" _green="\033[0;32m" _reset="\033[0m" _user="$(whoami)" If 2nd argument is set and enabled, allow debug command if [ "${#}" = "2" ]; then if [ "${2}" = "1" ]; then _debug="1" fi fi if [ "${DEBUG_COMMANDS}" = "1" ] || [ "${_debug}" = "1" ]; then printf "${_red}%s \$ ${_green}${_cmd}${_reset}\n" "${_user}" fi sh -c "LANG=C LC_ALL=C ${_cmd}" } log() { _lvl="${1}" _msg="${2}" _clr_ok="\033[0;32m" #绿 _clr_info="\033[0;34m" #蓝 _clr_warn="\033[0;33m" #黄 _clr_err="\033[0;31m" #红 _clr_rst="\033[0m" #结束标记 if [ "${_lvl}" = "ok" ]; then printf "${_clr_ok}[OK] %s${_clr_rst}\n" "${_msg}" elif [ "${_lvl}" = "info" ]; then printf "${_clr_info}[INFO] %s${_clr_rst}\n" "${_msg}" elif [ "${_lvl}" = "warn" ]; then printf "${_clr_warn}[WARN] %s${_clr_rst}\n" "${_msg}" 1>&2 # stdout -> stderr elif [ "${_lvl}" = "err" ]; then printf "${_clr_err}[ERR] %s${_clr_rst}\n" "${_msg}" 1>&2 # stdout -> stderr else printf "${_clr_err}[???] %s${_clr_rst}\n" "${_msg}" 1>&2 # stdout -> stderr fi } get_mysql_default_config() { _key="${1}" mysqld --verbose --help 2>/dev/null | awk -v key="${_key}" '$1 == key { print $2; exit }' } if [ "$MYSQL_ROOT_PASSWORD" = "**Random**" ]; then export MYSQL_ROOT_PASSWORD=`cat /dev/urandom | tr -dc A-Z-a-z-0-9 | head -c${1:-16}` fi if [ "$MYSQL_SOCKET_DIR"X = ""X ]; then export MYSQL_SOCKET_DIR=`get_mysql_default_config socket` fi if [ "$DB_DATA_DIR"X = ""X ]; then export DB_DATA_DIR="$( get_mysql_default_config "datadir" )" fi if [ -d "${DB_DATA_DIR}/mysql" ] && [ "$( ls -A "${DB_DATA_DIR}/mysql" )" ]; then log "info" "Found existing data directory. MySQL already setup." else log "info" "No existing MySQL data directory found. Setting up MySQL for the first time." Create datadir if not exist yet if [ ! -d "${DB_DATA_DIR}" ]; then log "info" "Creating empty data directory in: ${DB_DATA_DIR}." run "mkdir -p ${DB_DATA_DIR}" run "chown -R ${MY_USER}:${MY_GROUP} ${DB_DATA_DIR}" run "chmod 0777 ${MY_USER}:${MY_GROUP} ${DB_DATA_DIR}" fi initialize no password run "mysqld --initialize-insecure --datadir=${DB_DATA_DIR} --user=${MYSQL_USER}" run "mysqld --skip-networking &" for i in `seq 1 60`;do if echo 'SELECT 1' | mysql --protocol=socket -uroot > /dev/null 2>&1; then break fi log "info" "Initializing ..." sleep 1s i=$(( i + 1 )) done pid="$(pgrep mysqld | head -1)" if [ "${pid}" = "" ]; then log "err" "Could not find running MySQL PID." log "err" "MySQL init process failed." exit 1 fi Bootstrap MySQL log "info" "Setting up root user permissions." echo "DELETE FROM mysql.user ;" | mysql --protocol=socket -uroot echo "CREATE USER 'root'@'%' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ;" | mysql --protocol=socket -uroot echo "GRANT ALL ON *.* TO 'root'@'%' WITH GRANT OPTION ;" | mysql --protocol=socket -uroot echo "DROP DATABASE IF EXISTS test ;" | mysql --protocol=socket -uroot echo "FLUSH PRIVILEGES ;" | mysql --protocol=socket -uroot log "info" "Shutting down MySQL." run "kill -s TERM ${pid}" sleep 5 if pgrep mysqld >/dev/null 2>&1; then log "err" "Unable to shutdown MySQL server." log "err" "MySQL init process failed." exit 1 fi log "info" "MySQL successfully installed." fi log "info" "Starting $(mysqld --version)" log "info" "MYSQL Config: /etc/my.cnf" log "info" "MYSQL Password: $MYSQL_ROOT_PASSWORD" log "info" "MYSQL Data_db_dir: $DB_DATA_DIR" log "info" "Auther: https://github.com/fangdm/docker-mysql" exec mysqld |
运行之后,使用docker logs mysql
就可以看出mysql输出的密码。如果想自定义密码,可以使用-e MYSQL_ROOT_PASSWORD=password
来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
[root@host mysql]# docker logs mysql [INFO] No existing MySQL data directory found. Setting up MySQL for the first time. [INFO] Initializing ... [INFO] Setting up root user permissions. [INFO] Shutting down MySQL. [INFO] MySQL successfully installed. [INFO] Starting mysqld Ver 5.7.25 for Linux on x86_64 (MySQL Community Server (GPL)) [INFO] MYSQL Config: /etc/my.cnf [INFO] MYSQL Password: fVKtzGsS06fQ3o9c [INFO] MYSQL Data_db_dir: /var/lib/mysql/ [INFO] Auther: https://github.com/fangdm/docker-mysql [root@host mysql]# docker exec -it mysql mysql -uroot -pfVKtzGsS06fQ3o9c -e "show databases" mysql: [Warning] Using a password on the command line interface can be insecure. +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | +--------------------+ |
一、如果您发现本站侵害了相关版权,请附上本站侵权链接和您的版权证明一并发送至邮箱:yehes#qq.com(#替换为@)我们将会在五天内处理并断开该文章下载地址。
二、本站所有资源来自互联网整理收集,全部内容采用撰写共用版权协议,要求署名、非商业用途和相同方式共享,如转载请也遵循撰写共用协议。
三、根据署名-非商业性使用-相同方式共享 (by-nc-sa) 许可协议规定,只要他人在以原作品为基础创作的新作品上适用同一类型的许可协议,并且在新作品发布的显著位置,注明原作者的姓名、来源及其采用的知识共享协议,与该作品在本网站的原发地址建立链接,他人就可基于非商业目的对原作品重新编排、修改、节选或者本人的作品为基础进行创作和发布。
四、基于原作品创作的所有新作品都要适用同一类型的许可协议,因此适用该项协议, 对任何以他人原作为基础创作的作品自然同样都不得商业性用途。
五、根据二〇〇二年一月一日《计算机软件保护条例》规定:为了学习和研究软件内含的设计思想和原理,通过安装、显示、传输或者存储软件等方式使用软件的,可不经软件著作权人许可,无需向其支付报酬!
六、鉴此,也望大家按此说明转载和分享资源!本站提供的所有信息、教程、软件版权归原公司所有,仅供日常使用,不得用于任何商业用途,下载试用后请24小时内删除,因下载本站资源造成的损失,全部由使用者本人承担!