容器的基本概念
什么是容器?
容器就是一个视图隔离、资源可限制、独立文件系统的进程集合。所谓“视图隔离”就是能够看到部分进程以及具有独立的主机名等;控制资源使用率则是可以对于内存大小以及 CPU 使用个数等进行限制。容器就是一个进程集合,它将系统的其他资源隔离开来,具有自己独立的资源视图。
容器具有一个独立的文件系统,因为使用的是系统的资源,所以在独立的文件系统内不需要具备内核相关的代码或者工具,我们只需要提供容器所需的二进制文件、配置文件以及依赖即可。只要容器运行时所需的文件集合都能够具备,那么这个容器就能够运行起来。
如何为进程提供一个独立的运行环境呢?
- 针对不同进程使用同一个文件系统所造成的问题而言,Linux 和 Unix 操作系统可以通过 chroot 系统调用将子目录变成根目录,达到视图级别的隔离;进程在 chroot 的帮助下可以具有独立的文件系统,对于这样的文件系统进行增删改查不会影响到其他进程;
- 因为进程之间相互可见并且可以相互通信,使用 Namespace 技术来实现进程在资源的视图上进行隔离。在 chroot 和 Namespace 的帮助下,进程就能够运行在一个独立的环境下了;
- 但在独立的环境下,进程所使用的还是同一个操作系统的资源,一些进程可能会侵蚀掉整个系统的资源。为了减少进程彼此之间的影响,可以通过 Cgroup 来限制其资源使用率,设置其能够使用的 CPU 以及内存量。
什么是镜像?
我们将容器运行时所需要的所有的文件集合称之为容器镜像。
docker 把应用程序及其依赖,打包在 image 文件里面。只有通过这个文件,才能生成 Docker 容器。image 文件可以看作是容器的模板。Docker 根据 image 文件生成容器的实例。同一个 image 文件,可以生成多个同时运行的容器实例。
image 是二进制文件。实际开发中,一个 image 文件往往通过继承另一个 image 文件,加上一些个性化设置而生成。举例来说,你可以在 Ubuntu 的 image 基础上,往里面加入 Apache 服务器,形成你的 image。
那么,一般都是通过什么样的方式来构建镜像的呢?通常情况下,我们会采用 Dockerfile 来构建镜像,这是因为 Dockerfile 提供了非常便利的语法糖,能够帮助我们很好地描述构建的每个步骤。当然,每个构建步骤都会对已有的文件系统进行操作,这样就会带来文件系统内容的变化,我们将这些变化称之为 changeset。当我们把构建步骤所产生的变化依次作用到一个空文件夹上,就能够得到一个完整的镜像。
changeset 的分层以及复用特点能够带来几点优势
第一,能够提高分发效率,简单试想一下,对于大的镜像而言,如果将其拆分成各个小块就能够提高镜像的分发效率,这是因为镜像拆分之后就可以并行下载这些数据;
第二,因为这些数据是相互共享的,也就意味着当本地存储上包含了一些数据的时候,只需要下载本地没有的数据即可,举个简单的例子就是 golang 镜像是基于 alpine 镜像进行构建的,当本地已经具有了 alpine 镜像之后,在下载 golang 镜像的时候只需要下载本地 alpine 镜像中没有的部分即可;
第三,因为镜像数据是共享的,因此可以节约大量的磁盘空间,简单设想一下,当本地存储具有了 alpine 镜像和 golang 镜像,在没有复用的能力之前,alpine 镜像具有 5M 大小,golang 镜像有 300M 大小,因此就会占用 305M 空间;而当具有了复用能力之后,只需要 300M 空间即可。
容器的使用
本部分主要围绕学习赛所用到的命令进行讲解从而学习docker的使用流程
构建Docker镜像
在项目的根目录下,新建一个文本文件Dockerfile,其中包含一组指令来告诉 Docker 如何构建我们的镜像。Docker会根据该文件生成二进制的 image 文件。
# 指定基础镜像
FROM registry.cn-shanghai.aliyuncs.com/tcc-public/java:jdk_13.0.2
# 把当前文件夹里的文件拷贝到镜像的根目录下
ADD . /
# 指定接下来的工作路径为/(根目录)
WORKDIR /
# 指定镜像启动后要执行的命令
CMD ["sh","run.sh"]
构建启动后要执行的run.sh
run.sh内容
java -jar XX.jar
编写业务部分
这部分就是我们真正的项目内容,按照赛题来说,其实业务代码逻辑很简单,生成json就可以了,镜像下面也已经给好了csv文件,json这块我用了fastjson,打jar包的时候把依赖一起打进去就可以啦。
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class TC {
public static void main(String[] args) {
List<Integer> res = new ArrayList<>();
int sum = 0;
try (BufferedReader file = new BufferedReader(new InputStreamReader(new FileInputStream("/tcdata/num_list.csv"), "UTF-8"))) {
String record;
while ((record = file.readLine()) != null) {
res.add(Integer.parseInt(record));
sum += Integer.parseInt(record);
}
} catch (Exception e) {
}
Collections.sort(res,Collections.reverseOrder());
JSONObject jsonObject = new JSONObject();
JSONArray jsonArray = new JSONArray();
for(int i = 0;i < 10;i++){
if(i >= res.size())
break;
jsonArray.add(res.get(i));
}
jsonObject.put("Q1","Hello world");
jsonObject.put("Q2",sum);
jsonObject.put("Q3",jsonArray);
String sourceString = jsonObject.toJSONString(); //待写入字符串
byte[] sourceByte = sourceString.getBytes();
if(null != sourceByte) {
try {
File file = new File("result.json"); //文件路径(路径+文件名)
if (!file.exists()) { //文件不存在则创建文件,先创建目录
file.createNewFile();
}
FileOutputStream outStream = new FileOutputStream(file); //文件输出流用于将数据写入文件
outStream.write(sourceByte);
outStream.close(); //关闭文件输出流
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
本地测试
镜像抓取
镜像抓取命令
docker image pull library/hello-world
上面代码中,docker image pull是抓取 image 文件的命令。library/hello-world是 image 文件在仓库里面的位置,其中library是 image 文件所在的组,hello-world是 image 文件的名字。
Base Docker Image List中给出了主办方为我们提供的镜像列表,所以我们通过以下指令获取java镜像
docker pull registry.cn-shanghai.aliyuncs.com/tcc-public/java:jdk_13.0.2
本地构建
docker build -t registry.cn-shanghai.aliyuncs.com/wx_namespace/wx_namespace_learning:2 .
本地运行测试
这块我还有一点疑问,就是我docker run container后进入到容器中查看并没有发现/tcdata/num_list.csv文件,可是提交却能够通过,这块还不知道是为什么?
回答:跟群友交流得知,这个文件并不在提供给我们的镜像中,而是在评测环境中。
docker run -v /data:/tcdata your_image sh run.sh
可以通过 -v 把运行环境的某个路径映射成镜像里的/tcdata。比如这里我把我主机的/data目录映射成 /tcdata目录。
有点类似windows上面的映射网络驱动器。
sudo docker run –it registry.cn-shanghai.aliyuncs.com/wx_namespace/wx_namespace_learning:2 /bin/bash
-it参数:容器的 Shell 映射到当前的 Shell,然后你在本机窗口输入的命令,就会传入容器。
/bin/bash:容器启动以后,内部第一个执行的命令。这里是启动 Bash,保证用户可以使用 Shell。
我们可以通过run指令查看运行结果,进行本地测试。
构建镜像并推送
命令行推送
推送的话说起来其实很简单
docker push registry.cn-shenzhen.aliyuncs.com/test_for_tianchi/test_for_tianchi_submit:1.0
但是由于比赛推荐我们使用私有仓库,所以要先登录下阿里云账号
sudo docker login --username=XXX registry.cn-shanghai.aliyuncs.com
IDE + Cloud Toolkit方法
IDE + Cloud Toolkit
这种方式推送到镜像仓库比较便捷,具体可以见链接
提交运行
推送到镜像仓库后,在竞赛界面提交运行即可。