一、Docker简介
1、背景
物理机时代
一个物理机上安装操作系统,然后直接运行我们的软件。也就是说你电脑上直接跑了一个软件,并没有开虚拟机什么的,资源极其浪费。
缺点
- 部署慢
- 成本高
虚拟机都不开,直接上物理机部署应用,那成本能不高嘛
- 资源浪费
硬件资源没有合理利用
- 不易迁移和增加机器
每次迁移都要重新安装一模一样的运行环境等
- 受限于硬件
虚拟机时代
一个物理机上虚拟化出来多个虚拟机,每个虚拟机上进行安装操作系统,然后在虚拟机上直接运行我们的软件。比如阿里云、腾讯云等云服务器都是虚拟机。
特点
- 多部署
一个物理机上创建多个虚拟机,每个虚拟机的ip都不同,一个软件可以在不同虚拟机上进行部署。
- 资源隔离
每个虚拟机都由独立分配的内存磁盘等硬件资源,通过Hypervisor分配。
- 易扩展
随时随地都能在一个物理机上开出新的虚拟机。成本也低。
- 成本相对较低
缺点
- 虚拟机需要安装操作系统
假设我们的应用程序只有几KB,或者几十MB,但是却要在虚拟机上安装一个几百兆的操作系统来跑。
容器化时代
虚拟化技术是物理硬件的隔离,容器化技术是app层面的隔离,两者不冲突。往往容器化时代都是基于虚拟机基础之上做的资源隔离。它可以基于虚拟机的操作系统部署多服务,不再需要额外安装操作系。
特点
- 标准化的迁移方式
开发者将环境和程序打包给运维,运维直接执行就完事了。
- 统一的参数配置
- 自动化部署
- 应用集群监控
- 开发与运维之间的沟通桥梁
传统虚拟机和Docker的比较
2、Docker是什么
docker官方地址:https://www.docker.com/
Docker is an open platform for developing, shipping, and running applications. Docker enables you to separate your applications from your infrastructure so you can deliver software quickly. With Docker, you can manage your infrastructure in the same ways you manage your applications. By taking advantage of Docker’s methodologies for shipping, testing, and deploying code quickly, you can significantly reduce the delay between writing code and running it in production.
Docker 最初是 dotCloud 公司创始人 Solomon Hykes 在法国期间发起的一个公司内部项目,于 2013 年 3 月以 Apache 2.0 授权协议开源,主要项目代码在 GitHub 上进行维护。 Docker 使用 Google 公司推出的 Go 语言 进行开发实现。 Docker 是linux容器的一种封装,提供简单易用的容器使用接口。它是最流行的Linux容器解决方案。 Docker 的接口相当简单,用户可以方便的创建、销毁容器。 Docker 将应用程序与程序的依赖,打包在一个文件里面。运行这个文件就会生成一个虚拟容器。程序运行在虚拟容器里,如同在真实物理机上运行一样,有了Docker ,就不用担心环境问题了。
Docker concepts
Docker is a platform for developers and sysadmins to build, run, and share applications with containers. The use of containers to deploy applications is called containerization. Containers are not new, but their use for easily deploying applications is.
Containerization is increasingly popular because containers are:
- Flexible: Even the most complex applications can be containerized.
- 灵活:即使是最复杂的应用也可以集装箱化。
- Lightweight: Containers leverage and share the host kernel, making them much more efficient in terms of system resources than virtual machines.
- 轻量级:容器利用并共享主机内核,使它们在系统资源方面比虚拟机效率更高
- Portable: You can build locally, deploy to the cloud, and run anywhere.
- 便携式:您可以在本地构建,部署到云,并在任何地方运行
- Loosely coupled: Containers are highly self sufficient and encapsulated, allowing you to replace or upgrade one without disrupting others.
- 松耦合:容器是高度自给自足和封装的,允许您更换或升级一个容器,而不会中断其他容器。
- Scalable: You can increase and automatically distribute container replicas across a datacenter.
- 可扩展:您可以在数据中心增加并自动分发容器副本。
- Secure: Containers apply aggressive constraints and isolations to processes without any configuration required on the part of the user.
- 安全:容器对进程应用严格的约束和隔离,而不需要用户进行任何配置。
- 以上引自docker官方文档:https://docs.docker.com/get-started/
3、Docker应用场景
- web应用的自动化打包和发布
- 自动化测试和持续集成、发布
- 在服务型环境中部署和调整数据库或其他应用
4、Docker三大概念
Docker架构
- 镜像 image
- docker镜像就是一个只读模板,比如,一个镜像可以包含一个完整的centos,里面仅安装apache或用户的其他应用,镜像可以用来创建docker容器,另外docker提供了一个很简单的机制来创建镜像或者更新现有的镜像,用户甚至可以直接从其他人那里下载一个已经做好的镜像来直接使用。
- 容器 container
- docker利用容器来运行应用,容器是从镜像创建的运行实例,它可以被启动,开始、停止、删除、每个容器都是互相隔离的,保证安全的平台,可以把容器看做是简易版的linux环境(包括root用户权限、镜像空间、用户空间和网络空间等)和运行在其中的应用程序。
- 仓库 repository
- 镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry就是这样的服务。 一个Docker Registry中可以包含多个仓库( Repository) ; 每个仓库可以包含多个标签(Tag) ;每个标签对应一个镜像。通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过<仓库名>:<标签>的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以latest作为默认标签。
- 仓库分为两种,公有仓库和私有仓库,最大的公共仓库是docker Hub,存放了数量庞大的镜像供用户下载,国内的docker pool,这里仓库的概念与Git类似,registry可以理解为github这样的托管服务。
5、Docker优势
# 1. 更高效的利用系统资源
由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销,Docker 对系统 资源的利用率更高。
无论是应用执行速度、内存损耗或者文件存储速度,都要比传 统虚拟机技术更高效。因此,相比虚拟机技术,一个相同配置的主机,往往可以运 行更多数量的应用。
# 2. 更快速的启动时间
传统的虚拟机技术启动应用服务往往需要数分钟,而 Docker 容器应用,由于直接 运行于宿主内核,无需启动完整的操作系统,因此可以做到秒级、甚至毫秒级的启 动时间。大大的节约了开发、测试、部署的时间。
# 3. 一致的运行环境
开发过程中一个常见的问题是环境一致性问题。由于开发环境、测试环境、生产环 境不一致,导致有些 bug 并未在开发过程中被发现。
而 Docker 的镜像提供了除内 核外完整的运行时环境,确保了应用运行环境一致性,从而不会再出现 “这段代码 在我机器上没问题啊” 这类问题。
# 4. 持续交付和部署
对开发和运维(DevOps)人员来说,最希望的就是一次创建或配置,可以在任意 地方正常运行。
使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。开发人员 可以通过 Dockerfile 来进行镜像构建,并结合 持续集成(Continuous Integration) 系 统进行集成测试,
而运维人员则可以直接在生产环境中快速部署该镜像,甚至结合 持续部署(Continuous Delivery/Deployment) 系统进行自动部署。
而且使用 Dockerfile 使镜像构建透明化,不仅仅开发团队可以理解应用运行环 境,也方便运维团队理解应用运行所需条件,帮助更好的生产环境中部署该镜像。
# 5. 更轻松的迁移
由于 Docker 确保了执行环境的一致性,使得应用的迁移更加容易。Docker 可以在 很多平台上运行,无论是物理机、虚拟机、公有云、私有云,甚至是笔记本,其运 行结果是一致的。
因此用户可以很轻易的将在一个平台上运行的应用,迁移到另一 个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。
二、Docker安装
1、 环境准备
#系统环境:docker最低支持centos7且在64位平台上,内核版本在3.10以上
#版本:Docker Community Edition(CE)社区版,Enterprise Edition(EE) 企业版(包含了一些收费服务)
2、安装步骤
step1:使用yum命令进行安装
老版本安装方式 $ yum install -y docker 安装的版本是1.13.1(2017-2-8更新),不推荐
新版本安装:Docker官方文档:
https://docs.docker.com/engine/install/centos/ 步骤摘要:
#1.卸载旧版本
$ sudo yum remove docker
docker-client
docker-client-latest
docker-common
docker-latest
docker-latest-logrotate
docker-logrotate
docker-engine
#2.安装yum工具包包(-y 表示不询问 使用默认配置进行安装,等待提示安装完毕!)
$ sudo yum install -y yum-utils
#3.设置镜像的仓库(默认是从国外的,推荐使用国内的)
$ sudo yum-config-manager
--add-repo
https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
#更新yum软件包索引(可省略)
#yum makecache fast
#4.安装docker相关的 docker-ce 社区版 而ee是企业版
$ sudo yum install -y docker-ce docker-ce-cli containerd.io
#5.启动docker服务
$ sudo systemctl start docker
#6. 使用docker version查看docker版本
$ sudo docker version
#7. 测试
$ sudo docker run hello-world
step2:查看是否安装成功
yum list installed|grep docker
备注:命令为 查看yum安装完成文件列表 并 筛选docker安装信息 如下
step3:启动docker服务
systemctl start docker
step4:查看是否启动成功
systemctl status docker
如果没有出现 active(running)说明启动失败
解决方法:修改docker文件,具体命令:vi /etc/sysconfig/docker,在–selinux-enabled后面添加=false。如下图所示:
#再次执行
systemctl start docker
systemctl status docker
Step5:查看docker版本
docker version
Step6:测试
docker run hello-world
3、配置镜像加速
方式一:阿里云镜像加速
A. 打开阿里云网址
https://cr.console.aliyun.com
B. 使用支付宝或淘宝账号登录
C. 搜索容器镜像服务
D. 复制红色区域内容
E. 新建文件夹 mkdir -p /etc/docker
F. 编辑文件 vim /etc/docker/daemon.json 把刚刚复制的内容写进去保存
G. 重启docker
sudo systemctl daemon-reload
sudo systemctl restart docker
方式二:腾讯云镜像加速
腾讯云为您提供如下 DockerHub 加速器,以方便您快速拉取 DockerHub 平台上的容器镜像。 此加速器地址需在腾讯云云服务器中配置才可生效,请勿通过浏览器直接访问,请参照以下教程进行配置。
# 1.创建或修改 /etc/docker/daemon.json 文件,并写入以下内容:
{
"registry-mirrors": [
"https://mirror.ccs.tencentyun.com"
]
}
# 2.依次执行以下命令,重新启动 Docker 服务。
$ sudo systemctl daemon-reload
$ sudo systemctl restart docker
# Ubuntu16.04 请执行 sudo systemctl restart dockerd 命令
腾讯云容器服务官方地址:
https://cloud.tencent.com/document/product/457/9113
方式三:DaoCloud 加速器
简介:DaoCloud 加速器是广受欢迎的 Docker工具,解决了国内用户访问 Docker Hub 缓慢的问题。DaoCloud 加速器结合国内的 CDN 服务与协议层优化,成倍的提升了下载速度。
# 一条命令加速(记得重启docker)
curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://f1361db2.m.daocloud.io
注意:此时可能会导致启动失败,原因实在配置镜像加速时在配置文件中多加了一个”,”,删除重启即可。
三、Docker命令
1、帮助命令
docker version #显示docker的版本信息。
docker info #显示docker的系统信息,包括镜像和容器的数量
docker 命令 --help #帮助命令
2、镜像命令
docker images #查看所有本地主机上的镜像 可以使用docker image ls代替
Usage: docker images [OPTIONS] [REPOSITORY[:TAG]]
-q #只显示镜像id
-a #列出本地所有镜像(含中间映像层)
--digests #显示镜像的摘要信息
--no-trunc #显示镜像完整信息
[root@VM_0_5_centos ~]# docker images -a
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/hello-world latest bf756fb1ae65 5 months ago 13.3 kB
docker search #搜索镜像
Usage: docker search [OPTIONS] TERM
-f #根据指定条件删选镜像
[root@VM_0_5_centos ~]# docker search mysql -f stars=5000 #搜索MySQL镜像stars超过5000的
INDEX NAME STARS
docker.io docker.io/mysql 9623
docker pull #下载镜像 docker image pull
Usage: docker pull [OPTIONS] NAME[:TAG|@DIGEST]
[root@VM_0_5_centos ~]# docker pull tomcat:8.0 #如果不指定tag,默认下载最新版本
[root@VM_0_5_centos ~]# docker images -a
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/tomcat 9.0.36-jdk8-openjdk b79665757bae 4 days ago 530 MB
docker.io/tomcat latest 2eb5a120304e 4 days ago 647 MB
docker.io/hello-world latest bf756fb1ae65 5 months ago 13.3 kB
docker.io/tomcat 8.0 ef6a7c98d192 21 months ago 356 MB
docker rmi #删除镜像 docker image rm
Usage: docker rmi [OPTIONS] IMAGE [IMAGE...]
Remove one or more images
Options:
-f, --force #强制删除
[root@VM_0_5_centos ~]# docker rmi tomcat #删除最新版本的tomcat
[root@VM_0_5_centos ~]# docker rmi ef6a7c98d192 #根据镜像id删除指定的tomcat
[root@VM_0_5_centos ~]# docker rmi -f $(docker images -aq) #强制删除全部的镜像
3、容器命令
[root@VM_0_5_centos ~]# docker container --help
Usage: docker container COMMAND
Manage containers
Options:
--help Print usage
Commands:
attach Attach to a running container
commit Create a new image from a containers changes
cp Copy files/folders between a container and the local filesystem
create Create a new container
diff Inspect changes on a containers filesystem
exec Run a command in a running container
export Export a containers filesystem as a tar archive
inspect Display detailed information on one or more containers
kill Kill one or more running containers
logs Fetch the logs of a container
ls List containers
pause Pause all processes within one or more containers
port List port mappings or a specific mapping for the container
prune Remove all stopped containers
rename Rename a container
restart Restart one or more containers
rm Remove one or more containers
run Run a command in a new container
start Start one or more stopped containers
stats Display a live stream of container(s) resource usage statistics
stop Stop one or more running containers
top Display the running processes of a container
unpause Unpause all processes within one or more containers
update Update configuration of one or more containers
wait Block until one or more containers stop, then print their exit codes
Run docker container COMMAND --help for more information on a command.
启动容器
docker run 镜像id #新建容器并启动
Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
Options:
-d, --detach Run container in background and print
-e, --env list Set environment variables (default [])
-i, --interactive Keep STDIN open even if not attached
-p, --publish list Publish a containers port(s) to the host (default [])
-P, --publish-all Publish all exposed ports to random ports
--rm Automatically remove the container when it exits
-t, --tty Allocate a pseudo-TTY
-v, --volume list Bind mount a volume (default [])
--volume-driver string Optional volume driver for the container
--volumes-from list Mount volumes from the specified container(s) (default [])
[root@VM_0_5_centos ~]# docker run -it centos /bin/bash #启动centos容器并进入交互
[root@VM_0_5_centos ~]# docker run -d centos #后台启动centos容器,需要注意:docker容器使用后台运行,就必须要有要一个前台进程,docker发现当前容器没有前台进程使用,就会自动停止
进入容器
docker exec -it 容器id bashshell #进入当前正在运行的容器(开启一个新的终端)
docker attach 容器id #进入当前正在运行的容器(直接进入正在运行的终端)
查看运行的容器
docker ps #列出所有运行的容器 docker container list
Usage: docker ps [OPTIONS]
Options:
-a, --all Show all containers (default shows just running)
-f, --filter filter Filter output based on conditions provided
--format string Pretty-print containers using a Go template
--help Print usage
-n, --last int Show n last created containers (includes all states) (default -1)
-l, --latest Show the latest created container (includes all states)
--no-trunc Dont truncate output
-q, --quiet Only display numeric IDs
-s, --size Display total file sizes
[root@VM_0_5_centos ~]# docker ps -a
退出容器
exit #容器停止退出
ctrl +P +Q #容器不停止退出
删除容器
docker rm 容器id #删除指定的容器,不能删除正在运行的容器,如果要强制删除 rm -rf
docker rm -f $(docker ps -aq) #删除所有的容器
docker ps -a -q|xargs docker rm #删除所有的容器
容器管理
docker start 容器id #启动容器
docker restart 容器id #重启容器
docker stop 容器id #停止当前正在运行的容器
docker kill 容器id #强制停止当前容器
4、其他
[root@VM_0_5_centos ~]# docker logs --help
Usage: docker logs [OPTIONS] CONTAINER
Fetch the logs of a container
Options:
--details Show extra details provided to logs
-f, --follow Follow log output
--help Print usage
--since string Show logs since timestamp
--tail string Number of lines to show from the end of the logs (default "all")
-t, --timestamps Show timestamps
[root@VM_0_5_centos ~]docker run -d centos /bin/sh -c "while true;do echo hello;sleep 1;done" #模拟日志
-tf #显示日志信息(一直更新)
--tail number #需要显示日志条数
docker logs -t --tail n 容器id #查看n行日志
docker logs -ft 容器id #跟踪日志
docker top 容器id #查看容器中的进程信息
docker inspect 容器id #查看镜像元数据
#将指定容器内资源拷贝到主机
docker cp 容器id:容器内路径 主机目的路径
[root@VM_0_5_centos html]# docker cp 2d30adc4ee87:/etc/nginx/nginx.conf .
#将主机内资源拷贝到指定容器
docker cp 主机目的路径 容器id:容器内路径
[root@VM_0_5_centos html]# docker cp test.html d1cf09d31eda:/usr/local
docker save centos > /opt/centos.tar.gz # 导出docker镜像至本地
docker load < /opt/centos.tar.gz #导入本地镜像到docker镜像库
docker stats # 查看docker容器使用内存情况
四、Docker安装软件
1、安装mysql
#1. 搜索镜像 search 上hub.docker.com搜索
[root@localhost docker]# docker search mysql
#2. 拉取镜像 pull
[root@localhost docker]# docker pull mysql:5.7
#3、运行测试
# -d 后台运行
# --name 给容器命名
# -p 宿主机端口:容器内部端口
[root@localhost docker]# docker run -d -p 3355:3306 -e MYSQL_ROOT_PASSWORD=root --name mysql1 mysql:5.7
#测试 (关闭防火墙或授权远程访问) 外部连接访问正常
#进入容器命令:docker exec -it 容器id /bin/bash
------------------------------容器启动进行数据挂载-----------------------------
#mysql容器正常启动 数据挂载成功
$ docker run -d -p 3344:3306 -v /data/mysql/conf:/etc/mysql/conf.d -v /data/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root --name mysql01 mysql:5.7
# 开放3344端口及安全组,在容器内部创建用户并授权,测试OK
2、安装tomcat
#拉取镜像
[root@localhost docker]# docker pull tomcat
#创建容器运行
[root@localhost docker]# docker run -d -p 8080:8080 --name mytomcat tomcat
#测试访问(记得关闭防火墙 此时会发现访问成功出现404响应,原因是webapps下面没有资源)
ip:8080
#进入容器(可以使用容器名称或容器id)
[root@localhost docker]# docker exec -it mytomcat /bin/bash
#将webapps.dist下的内容复制到webapps下面,重新访问即可看到正常页面
root@ebef54554573:/usr/local/tomcat# cp -r webapps.dist/* webapps/
#部署项目到tomcat
[root@localhost docker]# docker cp demo.war mytomcat:/usr/local/tomcat/webapps/
#重启tomcat容器
[root@localhost docker]# docker restart mytomcat
3、安装nginx
#直接安装nginx镜像运行nginx容器
[root@localhost docker]# docker run -d -p 80:80 --name mynginx nginx
#进入容器 复制文件到容器同上,不在累述
#数据挂载 (把容器文件挂载到宿主机上面,不用每次进入容器内部修改文件,在容器外部宿主机修改文件会自动同步到容器内部,并且当删除容器后,宿主机上的文件不会被删除,相当于对容器内部文件进行持久化操作。)
[root@localhost docker]# docker run -d -p 8033:80 -v /data/nginx/html:/usr/share/nginx/html --name mynginx nginx
#在/data/nginx/html目录下创建test.html
[root@localhost html]# vim test.html
#测试成功
[root@localhost html]# curl localhost:8033/test.html
--------------------------------------------------------------------------------------
[root@localhost docker]# docker run -dit -p 8081:80 -v /data/nginx/conf:/etc/nginx -v /data/nginx/log:/var/log/nginx -v /data/nginx/html:/usr/share/nginx/html --name nginx1 nginx /bin/bash
# 说明:当挂载容器内安装软件配置目录容易出现很多问题,比如:1.后台启动即退出 2.成功启动后无法正常访问
[root@VM_0_5_centos conf]# curl localhost:8081
curl: (56) Recv failure: Connection reset by peer
4、安装elasticsearch
[root@VM_0_5_centos conf]# docker run -d -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms64m -Xmx512m" --name elasticsearch elasticsearch:7.6.2
[root@VM_0_5_centos conf]# curl localhost:9200
{
"name" : "b7546b5af212",
"cluster_name" : "docker-cluster",
"cluster_uuid" : "ZLQNTv0ZRbuQAXw4ZLJezw",
"version" : {
"number" : "7.6.2",
"build_flavor" : "default",
"build_type" : "docker",
"build_hash" : "ef48eb35cf30adf4db14086e8aabd07ef6fb113f",
"build_date" : "2020-03-26T06:34:37.794943Z",
"build_snapshot" : false,
"lucene_version" : "8.4.0",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}
五、Docker镜像
操作系统分为内核和用户空间。对于Linux而言,内核启动后,会挂载root文件系统为其提供用户空间支持。而Docker镜像(Image) ,就相当于是一个root文件系统。比如官方镜像centos:7.6就包含了完整的一套centos 7.6最小系统的root文件系统。
Docker镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。
因为镜像包含操作系统完整的root文件系统,其体积往往是庞大的,因此在Docker设计时将其设计为分层存储的架构。镜像只是一个虚拟的概念,其实际体现并非由一个文件组成,而是由一组文件系统组成,或者说,由多层文件系统联合组成。 镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。在构建镜像的时候,需要额外小心,每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。 分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。
镜像使用的是分层存储,容器也是如此。每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可以称这个为容器运行时读写而准备的存储层为容器存储层。容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。
镜像使用分层存储,便于资源共享,可以使用docker image inspect 命令查看
[root@VM_0_5_centos ~]# docker image inspect nginx
#docker commit 提交容器成为一个新的副本
docker commit -m="描述信息" -a="作者" 容器id 目标镜像名:[TAG]
[root@VM_0_5_centos ~]# docker commit -a="shuaige" -m="add test.html" 8fc157a704d4 nginx3:1.0
sha256:31b98e5522104b8a30b2cf587cc0494a265f4de9df3a7b04c45f78cb5b9001ef
[root@VM_0_5_centos ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx3 1.0 31b98e552210 14 seconds ago 132 MB
docker.io/nginx latest