SSTap

最近发现了个55R全局代理的软件感觉挺好用的

加个混淆免下流什么的...妥妥的

下载链接已放到Download页面

KVM安装Windows2003

Windows Server 2003 Enterprise Edition SP2 32bit

进入 Rescue 模式
SSH 登陆
sudo su

 

wget -O- https://down.vps.re/Windows/DD/2003/KVM/windows2003.32bit.raw | dd of=/dev/sda

 

DD完毕后,进入正常模式(Boot in normal mode)

远程桌面
用户名:administrator
密码:changeme

 

系统盘 5GB (觉得小,可自行动态调整)

 

注意:KVM VPS在linux系统内进行DD操作,请先执行fdisk -l,看下硬盘是否是virtio模式,显示 /dev/sda1,则 dd of=/dev/sda,显示是 /dev/vda1 则 dd of=/dev/vda

KVM安装Windows2008

Windows Server 2008 R2 Enterprise Edition 64bit

进入 Rescue 模式
SSH 登陆
sudo su

 

wget -O- https://down.vps.re/Windows/DD/2008/KVM/windows2008.R2.raw | dd of=/dev/sda

 

进度条显示100%完成后,点击 Boot in normal mode(进入正常模式)

DD完成后系统会自动重新识别安装硬件驱动,完成硬件驱动安装后会自动重启一次,大约5-10分钟后可以远程桌面

远程桌面
用户名:administrator
密码:Changeme!!!

系统盘 10GB

 

分区助手服务器版,安装后自行调整系统分区大小

这个模板包含 Intel全系列 Broadcom 57xx系列 网卡驱动 HP RAID卡 驱动 KVM virtio磁盘 virtio网卡 驱动 (适合所有基于KVM虚拟化VPS)

注意:KVM VPS在linux系统内进行DD操作,请先执行fdisk -l,看下硬盘是否是virtio模式,显示 /dev/sda1,则 dd of=/dev/sda,显示是 /dev/vda1 则 dd of=/dev/vda

【转载】Docker与Kubernetes系列(七): Docker Swarm

原文地址

这段时间工作中用到了Docker以及Kubernetes(简称K8S),现在整理下我学习Docker以及K8S过程中看的一些比较好的资料,方便自己回顾,也希望能给容器小白一些帮助。给自己定一个小目标,二月底之前完成。

这是本系列的第七篇文章, 先不介绍K8S了, 我打算先来介绍Docker Swarm(K8S以后再说吧)。

本篇文章基本翻译和总结自Docker官方文档, 做了精简和改动。

一、Swarm的一些核心概念

docker engine中已经内嵌了集群管理和编排的特性,加入到集群中的docker engine就是以swarm模式运行的。 我们可以以初始化一个swarm或者加入一个swarm的方式来使docker engine进入swarm模式。

一个Swarm就是一个docker engine的集群,我们可以在上面部署services。 我们也可以同时在同一台Docker实例上部署service和单独的docker容器。

Node

一个节点就是一台加入到swarm中的docker实例。
要在swarm上部署应用,我们需要向集群的管理节点提交我们对service的定义, manager node会把一个个的task(下文会说到)分发到集群的工作节点上。默认情况下,manager node同时也是一个worker node。

Service和Task

service是对于要运行在worker node上的task的定义, 当我们创建一个service的时候,我们需要制定要run哪个image,以及在这个container里要run什么commands。
在replicated service模式下,manager node会根据我们scale的大小来决定在各个节点上运行多少个task。
一个task包含一个docker容器以及在这个容器中要运行的指令。 它是swarm调度的原子单位。
Swarm模式有一个内部的DNS组件,这个组件可以自动把Swarm内的每个service作为一个DNS入口,manager node使用内部的负载均衡器把请求分发给各个worker node。

二、在Swarm上部署一个service

我们需要做以下的事情:
  • 初始化一个以swarm模式运行的docker engine集群
  • 向这个集群中加入两个worker node
  • 部署一个service

初始化Swarm

首先我们通过SSH登录到用来做manager node的机器上, 如 ssh root@10.66.137.222
然后运行以下指令创建一个swarm:
docker swarm init --advertise-addr 10.66.137.222

加入Worker nodes

我们在manager node上运行以下指令:
docker swarm join-token worker,
会输出:
docker swarm join \
--token XXX \
10.66.137.222:2377
这就是我们要在worker node上要运行的指令, 运行之后, worker node 就加入到了这个swarm集群中。
加入之后,在manager node上运行 docker node ls, 会有类似以下输出:
[root@localhost ~]# docker node ls
ID                           HOSTNAME               STATUS  AVAILABILITY  MANAGER STATUS
dsu1tginb4deet36qqzo4bbr3    localhost.localdomain  Ready   Active        Reachable
t2213ncsiy7oxfg73umm7fnyh    localhost.localdomain  Ready   Active
wbbl4h5qldptdaq6ogawvonhi *  localhost.localdomain  Ready   Active        Leader

部署service

比如 : docker service create --replicas 3 --name my-web -p 8080:80 nginx
我通过--replicas来定义task的数量,通过-p来使外部可以访问。之后我就可以通过http://10.66.137.222:8080/ 来访问到我的服务了:

【转载】Docker与Kubernetes系列(六): Docker的原理2_Linux CGroup

原文地址

这段时间工作中用到了Docker以及Kubernetes(简称K8S),现在整理下我学习Docker以及K8S过程中看的一些比较好的资料,方便自己回顾,也希望能给容器小白一些帮助。给自己定一个小目标,二月底之前完成。

这是本系列的第六篇文章, 将介绍Docker的原理之CGroup。(整理自:  http://coolshell.cn/articles/17049.html)

前面,我们介绍了Linux Namespace,但是Namespace解决的问题主要是环境隔离的问题,这只是虚拟化中最最基础的一步,我们还需要解决对计算机资源使用上的隔离。也就是说,虽然你通过Namespace把我Jail到一个特定的环境中去了,但是我在其中的进程使用用CPU、内存、磁盘等这些计算资源其实还是可以随心所欲的。所以,我们希望对进程进行资源利用上的限制或控制。这就是linux CGroup出来了的原因。

Linux CGroup全称Linux Control Group, 是Linux内核的一个功能,用来限制,控制与分离一个进程组群的资源(如CPU、内存、磁盘输入输出等)。

Linux CGroupCgroup 可​​​让​​​您​​​为​​​系​​​统​​​中​​​所​​​运​​​行​​​任​​​务​​​(进​​​程​​​)的​​​用​​​户​​​定​​​义​​​组​​​群​​​分​​​配​​​资​​​源​​​ — 比​​​如​​​ CPU 时​​​间​​​、​​​系​​​统​​​内​​​存​​​、​​​网​​​络​​​带​​​宽​​​或​​​者​​​这​​​些​​​资​​​源​​​的​​​组​​​合​​​。​​​您​​​可​​​以​​​监​​​控​​​您​​​配​​​置​​​的​​​ cgroup,拒​​​绝​​​ cgroup 访​​​问​​​某​​​些​​​资​​​源​​​,甚​​​至​​​在​​​运​​​行​​​的​​​系​​​统​​​中​​​动​​​态​​​配​​​置​​​您​​​的​​​ cgroup。

主要提供了如下功能:

  • Resource limitation: 限制资源使用,比如内存使用上限以及文件系统的缓存限制。
  • Prioritization: 优先级控制,比如:CPU利用和磁盘IO吞吐。
  • Accounting: 一些审计或一些统计,主要目的是为了计费。
  • Control: 挂起进程,恢复执行进程。

使​​​用​​​ cgroup,系​​​统​​​管​​​理​​​员​​​可​​​更​​​具​​​体​​​地​​​控​​​制​​​对​​​系​​​统​​​资​​​源​​​的​​​分​​​配​​​、​​​优​​​先​​​顺​​​序​​​、​​​拒​​​绝​​​、​​​管​​​理​​​和​​​监​​​控​​​。​​​可​​​更​​​好​​​地​​​根​​​据​​​任​​​务​​​和​​​用​​​户​​​分​​​配​​​硬​​​件​​​资​​​源​​​,提​​​高​​​总​​​体​​​效​​​率​​​。

在实践中,系统管理员一般会利用CGroup做下面这些事(有点像为某个虚拟机分配资源似的):

  • 隔离一个进程集合(比如:nginx的所有进程),并限制他们所消费的资源,比如绑定CPU的核。
  • 为这组进程 分配其足够使用的内存
  • 为这组进程分配相应的网络带宽和磁盘存储限制
  • 限制访问某些设备(通过设置设备的白名单)

那么CGroup是怎么干的呢?我们先来点感性认识吧。

首先,Linux把CGroup这个事实现成了一个file system,你可以mount。在我的Ubuntu 14.04下,你输入以下命令你就可以看到cgroup已为你mount好了。

1
2
3
4
5
6
7
8
9
10
11
12
hchen@ubuntu:~$ mount-t cgroup
cgroup on /sys/fs/cgroup/cpuset typecgroup (rw,relatime,cpuset)
cgroup on /sys/fs/cgroup/cpu typecgroup (rw,relatime,cpu)
cgroup on /sys/fs/cgroup/cpuacct typecgroup (rw,relatime,cpuacct)
cgroup on /sys/fs/cgroup/memory typecgroup (rw,relatime,memory)
cgroup on /sys/fs/cgroup/devices typecgroup (rw,relatime,devices)
cgroup on /sys/fs/cgroup/freezer typecgroup (rw,relatime,freezer)
cgroup on /sys/fs/cgroup/blkio typecgroup (rw,relatime,blkio)
cgroup on /sys/fs/cgroup/net_prio typecgroup (rw,net_prio)
cgroup on /sys/fs/cgroup/net_cls typecgroup (rw,net_cls)
cgroup on /sys/fs/cgroup/perf_event typecgroup (rw,relatime,perf_event)
cgroup on /sys/fs/cgroup/hugetlb typecgroup (rw,relatime,hugetlb)

或者使用lssubsys命令:

1
2
3
4
5
6
7
8
9
10
11
12
$ lssubsys  -m
cpuset /sys/fs/cgroup/cpuset
cpu /sys/fs/cgroup/cpu
cpuacct /sys/fs/cgroup/cpuacct
memory /sys/fs/cgroup/memory
devices /sys/fs/cgroup/devices
freezer /sys/fs/cgroup/freezer
blkio /sys/fs/cgroup/blkio
net_cls /sys/fs/cgroup/net_cls
net_prio /sys/fs/cgroup/net_prio
perf_event /sys/fs/cgroup/perf_event
hugetlb /sys/fs/cgroup/hugetlb

我们可以看到,在/sys/fs下有一个cgroup的目录,这个目录下还有很多子目录,比如: cpu,cpuset,memory,blkio……这些,这些都是cgroup的子系统。分别用于干不同的事的。

如果你没有看到上述的目录,你可以自己mount,下面给了一个示例:

1
2
3
4
5
6
7
8
mkdircgroup
mount-t tmpfs cgroup_root ./cgroup
mkdircgroup/cpuset
mount-t cgroup -ocpuset cpuset ./cgroup/cpuset/
mkdircgroup/cpu
mount-t cgroup -ocpu cpu ./cgroup/cpu/
mkdircgroup/memory
mount-t cgroup -omemory memory ./cgroup/memory/

一旦mount成功,你就会看到这些目录下就有好文件了,比如,如下所示的cpu和cpuset的子系统:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
hchen@ubuntu:~$ ls/sys/fs/cgroup/cpu/sys/fs/cgroup/cpuset/
/sys/fs/cgroup/cpu:
cgroup.clone_children  cgroup.sane_behavior  cpu.shares         release_agent
cgroup.event_control   cpu.cfs_period_us     cpu.stat           tasks
cgroup.procs           cpu.cfs_quota_us      notify_on_release  user
/sys/fs/cgroup/cpuset/:
cgroup.clone_children  cpuset.mem_hardwall             cpuset.sched_load_balance
cgroup.event_control   cpuset.memory_migrate           cpuset.sched_relax_domain_level
cgroup.procs           cpuset.memory_pressure          notify_on_release
cgroup.sane_behavior   cpuset.memory_pressure_enabled  release_agent
cpuset.cpu_exclusive   cpuset.memory_spread_page       tasks
cpuset.cpus            cpuset.memory_spread_slab       user
cpuset.mem_exclusive   cpuset.mems

你可以到/sys/fs/cgroup的各个子目录下去make个dir,你会发现,一旦你创建了一个子目录,这个子目录里又有很多文件了。

1
2
3
4
5
hchen@ubuntu:/sys/fs/cgroup/cpu$ sudomkdirhaoel
[sudo] password forhchen:
hchen@ubuntu:/sys/fs/cgroup/cpu$ ls./haoel
cgroup.clone_children  cgroup.procs       cpu.cfs_quota_us  cpu.stat           tasks
cgroup.event_control   cpu.cfs_period_us  cpu.shares        notify_on_release

好了,我们来看几个示例。

CPU 限制

假设,我们有一个非常吃CPU的程序,叫deadloop,其源码如下:

DEADLOOP.C
1
2
3
4
5
6
intmain(void)
{
    inti = 0;
    for(;;) i++;
    return0;
}

用sudo执行起来后,毫无疑问,CPU被干到了100%(下面是top命令的输出)

1
2
PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
3529 root      20   0    4196    736    656 R 99.6  0.1   0:23.13 deadloop

然后,我们这前不是在/sys/fs/cgroup/cpu下创建了一个haoel的group。我们先设置一下这个group的cpu利用的限制:

1
2
3
hchen@ubuntu:~# cat /sys/fs/cgroup/cpu/haoel/cpu.cfs_quota_us
-1
root@ubuntu:~# echo 20000 > /sys/fs/cgroup/cpu/haoel/cpu.cfs_quota_us

我们看到,这个进程的PID是3529,我们把这个进程加到这个cgroup中:

1
# echo 3529 >> /sys/fs/cgroup/cpu/haoel/tasks

然后,就会在top中看到CPU的利用立马下降成20%了。(前面我们设置的20000就是20%的意思)

1
2
PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
3529 root      20   0    4196    736    656 R 19.9  0.1   8:06.11 deadloop

下面的代码是一个线程的示例:

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
#define _GNU_SOURCE         /* See feature_test_macros(7) */
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/syscall.h>
constintNUM_THREADS = 5;
void*thread_main(void*threadid)
{
    /* 把自己加入cgroup中(syscall(SYS_gettid)为得到线程的系统tid) */
    charcmd[128];
    sprintf(cmd, "echo %ld >> /sys/fs/cgroup/cpu/haoel/tasks", syscall(SYS_gettid));
    system(cmd);
    sprintf(cmd, "echo %ld >> /sys/fs/cgroup/cpuset/haoel/tasks", syscall(SYS_gettid));
    system(cmd);
    longtid;
    tid = (long)threadid;
    printf("Hello World! It's me, thread #%ld, pid #%ld!\n", tid, syscall(SYS_gettid));
    inta=0;
    while(1) {
        a++;
    }
    pthread_exit(NULL);
}
intmain (intargc, char*argv[])
{
    intnum_threads;
    if(argc > 1){
        num_threads = atoi(argv[1]);
    }
    if(num_threads<=0 || num_threads>=100){
        num_threads = NUM_THREADS;
    }
    /* 设置CPU利用率为50% */
    mkdir("/sys/fs/cgroup/cpu/haoel", 755);
    system("echo 50000 > /sys/fs/cgroup/cpu/haoel/cpu.cfs_quota_us");
    mkdir("/sys/fs/cgroup/cpuset/haoel", 755);
    /* 限制CPU只能使用#2核和#3核 */
    system("echo \"2,3\" > /sys/fs/cgroup/cpuset/haoel/cpuset.cpus");
    pthread_t* threads = (pthread_t*) malloc(sizeof(pthread_t)*num_threads);
    intrc;
    longt;
    for(t=0; t<num_threads; t++){
        printf("In main: creating thread %ld\n", t);
        rc = pthread_create(&threads[t], NULL, thread_main, (void*)t);
        if(rc){
            printf("ERROR; return code from pthread_create() is %d\n", rc);
            exit(-1);
        }
    }
    /* Last thing that main() should do */
    pthread_exit(NULL);
    free(threads);
}

内存使用限制

我们再来看一个限制内存的例子(下面的代码是个死循环,其它不断的分配内存,每次512个字节,每次休息一秒):

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
intmain(void)
{
    intsize = 0;
    intchunk_size = 512;
    void*p = NULL;
    while(1) {
        if((p = malloc(p, chunk_size)) == NULL) {
            printf("out of memory!!\n");
            break;
        }
        memset(p, 1, chunk_size);
        size += chunk_size;
        printf("[%d] - memory is allocated [%8d] bytes \n", getpid(), size);
        sleep(1);
    }
    return0;
}

然后,在我们另外一边:

1
2
3
4
5
6
# 创建memory cgroup
$ mkdir/sys/fs/cgroup/memory/haoel
$ echo64k > /sys/fs/cgroup/memory/haoel/memory.limit_in_bytes
# 把上面的进程的pid加入这个cgroup
$ echo[pid] > /sys/fs/cgroup/memory/haoel/tasks

你会看到,一会上面的进程就会因为内存问题被kill掉了。

磁盘I/O限制

我们先看一下我们的硬盘IO,我们的模拟命令如下:(从/dev/sda1上读入数据,输出到/dev/null上)

1
sudoddif=/dev/sda1of=/dev/null

我们通过iotop命令我们可以看到相关的IO速度是55MB/s(虚拟机内):

1
2
TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND
8128 be/4 root       55.74 M/s   0.00 B/s 0.00 % 85.65 % ddif=/de~=/dev/null...

然后,我们先创建一个blkio(块设备IO)的cgroup

1
mkdir/sys/fs/cgroup/blkio/haoel

并把读IO限制到1MB/s,并把前面那个dd命令的pid放进去(注:8:0 是设备号,你可以通过ls -l /dev/sda1获得):

1
2
root@ubuntu:~# echo '8:0 1048576'  > /sys/fs/cgroup/blkio/haoel/blkio.throttle.read_bps_device
root@ubuntu:~# echo 8128 > /sys/fs/cgroup/blkio/haoel/tasks

再用iotop命令,你马上就能看到读速度被限制到了1MB/s左右。

1
2
TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND
8128 be/4 root      973.20 K/s   0.00 B/s 0.00 % 94.41 % ddif=/de~=/dev/null...

CGroup的子系统

好了,有了以上的感性认识我们来,我们来看看control group有哪些子系统:

  • blkio — 这​​​个​​​子​​​系​​​统​​​为​​​块​​​设​​​备​​​设​​​定​​​输​​​入​​​/输​​​出​​​限​​​制​​​,比​​​如​​​物​​​理​​​设​​​备​​​(磁​​​盘​​​,固​​​态​​​硬​​​盘​​​,USB 等​​​等​​​)。
  • cpu — 这​​​个​​​子​​​系​​​统​​​使​​​用​​​调​​​度​​​程​​​序​​​提​​​供​​​对​​​ CPU 的​​​ cgroup 任​​​务​​​访​​​问​​​。​​​
  • cpuacct — 这​​​个​​​子​​​系​​​统​​​自​​​动​​​生​​​成​​​ cgroup 中​​​任​​​务​​​所​​​使​​​用​​​的​​​ CPU 报​​​告​​​。​​​
  • cpuset — 这​​​个​​​子​​​系​​​统​​​为​​​ cgroup 中​​​的​​​任​​​务​​​分​​​配​​​独​​​立​​​ CPU(在​​​多​​​核​​​系​​​统​​​)和​​​内​​​存​​​节​​​点​​​。​​​
  • devices — 这​​​个​​​子​​​系​​​统​​​可​​​允​​​许​​​或​​​者​​​拒​​​绝​​​ cgroup 中​​​的​​​任​​​务​​​访​​​问​​​设​​​备​​​。​​​
  • freezer — 这​​​个​​​子​​​系​​​统​​​挂​​​起​​​或​​​者​​​恢​​​复​​​ cgroup 中​​​的​​​任​​​务​​​。​​​
  • memory — 这​​​个​​​子​​​系​​​统​​​设​​​定​​​ cgroup 中​​​任​​​务​​​使​​​用​​​的​​​内​​​存​​​限​​​制​​​,并​​​自​​​动​​​生​​​成​​​​​内​​​存​​​资​​​源使用​​​报​​​告​​​。​​​
  • net_cls — 这​​​个​​​子​​​系​​​统​​​使​​​用​​​等​​​级​​​识​​​别​​​符​​​(classid)标​​​记​​​网​​​络​​​数​​​据​​​包​​​,可​​​允​​​许​​​ Linux 流​​​量​​​控​​​制​​​程​​​序​​​(tc)识​​​别​​​从​​​具​​​体​​​ cgroup 中​​​生​​​成​​​的​​​数​​​据​​​包​​​。​​​
  • net_prio — 这个子系统用来设计网络流量的优先级
  • hugetlb — 这个子系统主要针对于HugeTLB系统进行限制,这是一个大页文件系统。​​​

注意,你可能在Ubuntu 14.04下看不到net_cls和net_prio这两个cgroup,你需要手动mount一下:

1
2
3
4
5
6
7
$ sudomodprobe cls_cgroup
$ sudomkdir/sys/fs/cgroup/net_cls
$ sudomount-t cgroup -o net_cls none /sys/fs/cgroup/net_cls
$ sudomodprobe netprio_cgroup
$ sudomkdir/sys/fs/cgroup/net_prio
$ sudomount-t cgroup -o net_prio none /sys/fs/cgroup/net_prio

关于各个子系统的参数细节,以及更多的Linux CGroup的文档,你可以看看下面的文档:

CGroup的术语

CGroup有下述术语:

  • 任务(Tasks):就是系统的一个进程。
  • 控制组(Control Group):一组按照某种标准划分的进程,比如官方文档中的Professor和Student,或是WWW和System之类的,其表示了某进程组。Cgroups中的资源控制都是以控制组为单位实现。一个进程可以加入到某个控制组。而资源的限制是定义在这个组上,就像上面示例中我用的haoel一样。简单点说,cgroup的呈现就是一个目录带一系列的可配置文件。
  • 层级(Hierarchy):控制组可以组织成hierarchical的形式,既一颗控制组的树(目录结构)。控制组树上的子节点继承父结点的属性。简单点说,hierarchy就是在一个或多个子系统上的cgroups目录树。
  • 子系统(Subsystem):一个子系统就是一个资源控制器,比如CPU子系统就是控制CPU时间分配的一个控制器。子系统必须附加到一个层级上才能起作用,一个子系统附加到某个层级以后,这个层级上的所有控制族群都受到这个子系统的控制。Cgroup的子系统可以有很多,也在不断增加中。

【转载】Docker与Kubernetes系列(五): Docker的原理1_Linux Namespace

原文地址

这段时间工作中用到了Docker以及Kubernetes(简称K8S),现在整理下我学习Docker以及K8S过程中看的一些比较好的资料,方便自己回顾,也希望能给容器小白一些帮助。给自己定一个小目标,二月底之前完成。

这是本系列的第五篇文章, 将介绍Docker的原理之Namespace。(整理自:http://coolshell.cn/articles/17010.html)

docker容器本质上是宿主机上的进程。Docker 通过namespace实现了资源隔离。

一、简介

Linux Namespace是linux提供的一种内核级别环境隔离的方法。不知道你是否还记得很早以前的Unix有一个叫chroot的系统调用(通过修改根目录把用户jail到一个特定目录下),chroot提供了一种简单的隔离模式:chroot内部的文件系统无法访问外部的内容。Linux Namespace在此基础上,提供了对UTS、IPC、mount、PID、network、User等的隔离机制。

举个例子,我们都知道,Linux下的超级父亲进程的PID是1,所以,同chroot一样,如果我们可以把用户的进程空间jail到某个进程分支下,并像chroot那样让其下面的进程 看到的那个超级父进程的PID为1,于是就可以达到资源隔离的效果了(不同的PID namespace中的进程无法看到彼此)

šLinux Namespace 有如下种类,官方文档在这里《Namespace in Operation

分类 系统调用参数 相关内核版本
Mount namespaces CLONE_NEWNS Linux 2.4.19
šUTS namespaces CLONE_NEWUTS Linux 2.6.19
IPC namespaces CLONE_NEWIPC Linux 2.6.19
PID namespaces CLONE_NEWPID Linux 2.6.24
Network namespaces CLONE_NEWNET 始于Linux 2.6.24 完成于 Linux 2.6.29
User namespaces CLONE_NEWUSER 始于 Linux 2.6.23 完成于 Linux 3.8)
  1. UTS: hostname
  2. IPC: 进程间通信
  3. PID: "chroot"进程树
  4. NS: 挂载点,首次登陆Linux
  5. NET: 网络访问,包括接口
  6. USER: 将本地的虚拟user-id映射到真实的user-id

主要是š三个系统调用

  • šclone() – 实现线程的系统调用,用来创建一个新的进程,并可以通过设计上述参数达到隔离。
  • šunshare() – 使某进程脱离某个namespace
  • šsetns() – 把某进程加入到某个namespace

二、几种Namespace

clone()系统调用

首先,我们来看一下一个最简单的clone()系统调用的示例,(后面,我们的程序都会基于这个程序做修改):

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
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
/* 定义一个给 clone 用的栈,栈大小1M */
#define STACK_SIZE (1024 * 1024)
staticcharcontainer_stack[STACK_SIZE];
charconstcontainer_args[] = {
    "/bin/bash",
    NULL
};
intcontainer_main(void* arg)
{
    printf("Container - inside the container!\n");
    /* 直接执行一个shell,以便我们观察这个进程空间里的资源是否被隔离了 */
    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return1;
}
intmain()
{
    printf("Parent - start a container!\n");
    /* 调用clone函数,其中传出一个函数,还有一个栈空间的(为什么传尾指针,因为栈是反着的) */
    intcontainer_pid = clone(container_main, container_stack+STACK_SIZE, SIGCHLD, NULL);
    /* 等待子进程结束 */
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return0;
}

从上面的程序,我们可以看到,这和pthread基本上是一样的玩法。但是,对于上面的程序,父子进程的进程空间是没有什么差别的,父进程能访问到的子进程也能。

下面, 让我们来看几个例子看看,Linux的Namespace是什么样的。

UTS Namespace

下面的代码,我略去了上面那些头文件和数据结构的定义,只有最重要的部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
intcontainer_main(void* arg)
{
    printf("Container - inside the container!\n");
    sethostname("container",10); /* 设置hostname */
    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return1;
}
intmain()
{
    printf("Parent - start a container!\n");
    intcontainer_pid = clone(container_main, container_stack+STACK_SIZE,
            CLONE_NEWUTS | SIGCHLD, NULL); /*启用CLONE_NEWUTS Namespace隔离 */
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return0;
}
 

运行上面的程序你会发现(需要root权限),子进程的hostname变成了 Container

1
2
3
4
5
6
7
hchen@ubuntu:~$ sudo./uts
Parent - start a container!
Container - inside the container!
root@container:~# hostname
container
root@container:~# uname -n
container

IPC Namespace

IPC全称 Inter-Process Communication,是Unix/Linux下进程间通信的一种方式,IPC有共享内存、信号量、消息队列等方法。所以,为了隔离,我们也需要把IPC给隔离开来,这样,只有在同一个Namespace下的进程才能相互通信。如果你熟悉IPC的原理的话,你会知道,IPC需要有一个全局的ID,即然是全局的,那么就意味着我们的Namespace需要对这个ID隔离,不能让别的Namespace的进程看到。

要启动IPC隔离,我们只需要在调用clone时加上CLONE_NEWIPC参数就可以了。

1
2
intcontainer_pid = clone(container_main, container_stack+STACK_SIZE,
            CLONE_NEWUTS | CLONE_NEWIPC | SIGCHLD, NULL);

首先,我们先创建一个IPC的Queue(如下所示,全局的Queue ID是0)

1
2
3
4
5
6
7
hchen@ubuntu:~$ ipcmk -Q
Message queue id: 0
hchen@ubuntu:~$ ipcs -q
------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages
0xd0d56eb2 0          hchen      644        0            0

如果我们运行没有CLONE_NEWIPC的程序,我们会看到,在子进程中还是能看到这个全启的IPC Queue。

1
2
3
4
5
6
7
8
9
hchen@ubuntu:~$ sudo./uts
Parent - start a container!
Container - inside the container!
root@container:~# ipcs -q
------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages
0xd0d56eb2 0          hchen      644        0            0

但是,如果我们运行加上了CLONE_NEWIPC的程序,我们就会下面的结果:

1
2
3
4
5
6
7
8
root@ubuntu:~$ sudo./ipc
Parent - start a container!
Container - inside the container!
root@container:~/linux_namespace# ipcs -q
------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages

我们可以看到IPC已经被隔离了。

PID Namespace

我们继续修改上面的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
intcontainer_main(void* arg)
{
    /* 查看子进程的PID,我们可以看到其输出子进程的 pid 为 1 */
    printf("Container [%5d] - inside the container!\n", getpid());
    sethostname("container",10);
    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return1;
}
intmain()
{
    printf("Parent [%5d] - start a container!\n", getpid());
    /*启用PID namespace - CLONE_NEWPID*/
    intcontainer_pid = clone(container_main, container_stack+STACK_SIZE,
            CLONE_NEWUTS | CLONE_NEWPID | SIGCHLD, NULL);
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return0;
}

运行结果如下(我们可以看到,子进程的pid是1了):

1
2
3
4
5
hchen@ubuntu:~$ sudo./pid
Parent [ 3474] - start a container!
Container [    1] - inside the container!
root@container:~# echo $$
1

你可能会问,PID为1有个毛用啊?我们知道,在传统的UNIX系统中,PID为1的进程是init,地位非常特殊。他作为所有进程的父进程,有很多特权(比如:屏蔽信号等),另外,其还会为检查所有进程的状态,我们知道,如果某个子进程脱离了父进程(父进程没有wait它),那么init就会负责回收资源并结束这个子进程。所以,要做到进程空间的隔离,首先要创建出PID为1的进程,最好就像chroot那样,把子进程的PID在容器内变成1。

但是,我们会发现,在子进程的shell里输入ps,top等命令,我们还是可以看得到所有进程。说明并没有完全隔离。这是因为,像ps, top这些命令会去读/proc文件系统,所以,因为/proc文件系统在父进程和子进程都是一样的,所以这些命令显示的东西都是一样的。

所以,我们还需要对文件系统进行隔离。

Mount Namespace

下面的例程中,我们在启用了mount namespace并在子进程中重新mount了/proc文件系统。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
intcontainer_main(void* arg)
{
    printf("Container [%5d] - inside the container!\n", getpid());
    sethostname("container",10);
    /* 重新mount proc文件系统到 /proc下 */
    system("mount -t proc proc /proc");
    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return1;
}
intmain()
{
    printf("Parent [%5d] - start a container!\n", getpid());
    /* 启用Mount Namespace - 增加CLONE_NEWNS参数 */
    intcontainer_pid = clone(container_main, container_stack+STACK_SIZE,
            CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, NULL);
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return0;
}

运行结果如下:

1
2
3
4
5
6
7
hchen@ubuntu:~$ sudo./pid.mnt
Parent [ 3502] - start a container!
Container [    1] - inside the container!
root@container:~# ps -elf
F S UID        PID  PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY          TIME CMD
4 S root         1     0  0  80   0 -  6917 wait   19:55 pts/2    00:00:00/bin/bash
0 R root        14     1  0  80   0 -  5671 -      19:56 pts/2    00:00:00 ps-elf

上面,我们可以看到只有两个进程 ,而且pid=1的进程是我们的/bin/bash。我们还可以看到/proc目录下也干净了很多:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
root@container:~# ls /proc
1          dma          key-users  net            sysvipc
16         driver       kmsg        pagetypeinfo   timer_list
acpi       execdomains  kpagecount  partitions     timer_stats
asound     fb           kpageflags  sched_debug    tty
buddyinfo  filesystems  loadavg     schedstat      uptime
bus        fs           locks       scsi           version
cgroups    interrupts   mdstat      self           version_signature
cmdline    iomem        meminfo     slabinfo       vmallocinfo
consoles   ioports      misc        softirqs       vmstat
cpuinfo    irq          modules     stat           zoneinfo
crypto     kallsyms     mounts      swaps
devices    kcore        mpt         sys
diskstats  keys         mtrr        sysrq-trigger

下图,我们也可以看到在子进程中的top命令只看得到两个进程了。

这里,多说一下。在通过CLONE_NEWNS创建mount namespace后,父进程会把自己的文件结构复制给子进程中。而子进程中新的namespace中的所有mount操作都只影响自身的文件系统,而不对外界产生任何影响。这样可以做到比较严格地隔离。

【转载】 Docker与Kubernetes系列(四): Docker的数据卷

原文地址

这段时间工作中用到了Docker以及Kubernetes(简称K8S),现在整理下我学习Docker以及K8S过程中看的一些比较好的资料,方便自己回顾,也希望能给容器小白一些帮助。给自己定一个小目标,二月底之前完成。

这是本系列的第四篇文章, 将介绍Docker的数据卷。

整理自: https://hujb2000.gitbooks.io/docker-flow-evolution/content/cn/advance/volume/preface.html

由于容器默认使用的AUFS文件系统的设计使得Docker可以提高镜像构建、存储和分发的效率,节省了时间和存储空间,然而也存在以下问题:

  1. 容器中的文件在宿主机上存在形式复杂,不能在宿主机上很方便地对容器中的文件进行访问;
  2. 多个容器之间的数据无法方便的共享;
  3. 当删除容器时,容器产生的数据将丢失;

于是,Docker引入了数据卷(volume)机制(vfs文件系统,不支持写时复制)。

一、数据卷的作用

数据卷是存在于一个或多个容器中的特定的目录,这一目录绕过UFS,可以提供以下特性:

  1. 数据卷可以在容器之间共享和重用;
  2. 对数据卷的修改会立马生效;
  3. 对数据卷的更新,不会影响镜像;
  4. 数据卷是被设计用来持久化数据的,它的生命周期独立于容器,默认会一直存在,即使容器被删除。

*注意:数据卷的使用,类似于 Linux 下对目录或文件进行 mount,镜像中的被指定为挂载点的目录中的文件会隐藏掉,能显示看的是挂载的数据卷。

二、数据卷的原理

Docker的数据的本质是容器中一个特殊的文件或目录(挂载点)。在容器的创建过程中,这个挂载点会被 挂载一个宿主机上的指定的目录 (一个以volumeID为名称的目录 或者指定的宿主机目录)。

例1: docker run -v /data busybox /bin/sh

//将宿主机上的volume_id目录绑定挂载到rootfs中指定的挂载点/data上 mount("/var/lib/docker/vfs/dir/volume_id","rootfs/data","none",MS_BIND,NULL);

例2: docker run -v /var/log:/data busybox /bin/bash

//将宿主机上的/var/log目录绑定挂载到rootfs中指定的挂载点/data上 mount("/var/log","rootfs/data","none",MS_BIND,NULL);

以上两种挂载方法的区别除了挂载的源目录不一样外,root/data目录下原来的文件在例1下是不存在的,但在例2下是仍旧存在的。

  • 创建Volume

volume的来源只有3种:即从容器挂载、从宿主机挂载和其他容器共享。内部通过Mount对象来维护逻辑。

  • 删除Volume

如果删除容器时带有-v标签或是这个容器运行时带有一个--rm标签,删除容器时会尝试删除这个容器所使用的volum。在将自己从volume的Container列表中删除后,判断volume的Container的列表是否为空,如果这个volume不被任务容器使用,则将这个volume删除 ,然后做以下两件事:

  1. 删除这个volume对应的配置文件;
  2. 如果这个volume是从容器挂载的,所以只需要删除宿主机上对应的volume_id文件夹。

  3. volume的相关配置文件

Docker的每个容器在docker/containers文件夹下有一个以容器ID命名的子文件夹,这个子文件夹中的config.json文件是这个容器的配置文件,可以从中看到这个容器所使用的volume ID以及它们的可写情况。 如果你要查看volume的具体信息,你可以在docker/volumes文件夹下找与volume id命名的子文件夹,这个子文件夹中的config.json文件包含了volume的具体信息。

 

三、数据卷的使用

  • 挂载一个主机目录作为数据据卷

$ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp:ro training/webapp Python app.py

Docker 挂载数据卷的默认权限是读写,用户也可以通过 :ro 指定为只读。

当然, 一次可以挂载多个数据卷。

查看数据卷的具体信息,docker inspect web 查看volume字段信息。

  • 挂载一个本地主机文件作为数据卷

$ sudo docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash

  • 数据卷容器

首先, 创建一个名为dbdata的数据卷容器

sudo docker run -d -v /dbdata --name dbdata training/postgres echo Data-only container for postgres

然后,在其他容器中使用 --volumes-from 来挂载 dbdata 容器中的数据卷。

$ sudo docker run -d --volumes-from dbdata --name db1 training/postgres

$ sudo docker run -d --volumes-from dbdata --name db2 training/postgres

可以使用超过一个的 --volumes-from 参数来指定从多个容器挂载不同的数据卷。 也可以从其他已经挂载了数据卷的容器来级联挂载数据卷。

$ sudo docker run -d --name db3 --volumes-from db1 training/postgres

注意:使用 --volumes-from 参数所挂载数据卷的容器自己并不需要保持在运行状态。如果删除了挂载的容器(包括 dbdata、db1 和 db2),数据卷并不会被自动删除。如果要删除一个数据卷,必须在删除最后一个还挂载着它的容器时使用 docker rm -v 命令来指定同时删除关联的容器。

  • 数据卷备份

首先使用 --volumes-from 标记来创建一个加载 dbdata 容器卷的容器,并从主机挂载当前目录到容器的 /backup 目录。命令如下:

$ sudo docker run --volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata

容器启动后,使用了 tar 命令来将 dbdata 卷备份为容器中 /backup/backup.tar 文件,也就是主机当前目录下的名为 backup.tar 的文件。

  • 数据卷恢复

如果要恢复数据到一个容器,首先创建一个带有空数据卷的容器 dbdata2。

$ sudo docker run -v /dbdata --name dbdata2 ubuntu /bin/bash

然后创建另一个容器,挂载 dbdata2 容器卷中的数据卷,并使用 untar 解压备份文件到挂载的容器卷中。

$ sudo docker run --volumes-from dbdata2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar

为了查看/验证恢复的数据,可以再启动一个容器挂载同样的容器卷来查看+

$ sudo docker run --volumes-from dbdata2 busybox /bin/ls /dbdata

【转载】Docker与Kubernetes系列(三): 外部访问Docker容器

原文地址

这段时间工作中用到了Docker以及Kubernetes(简称K8S),现在整理下我学习Docker以及K8S过程中看的一些比较好的资料,方便自己回顾,也希望能给容器小白一些帮助。给自己定一个小目标,二月底之前完成。

这是本系列的第三篇文章, 将介绍如何访问Docker容器。

整理自:  https://www.gitbook.com/book/yeasy/docker_practice/details

一、外部访问容器

容器中可以运行一些网络应用,要让外部也可以访问这些应用,可以通过 -P 或 -p 参数来指定端口映射。
当使用 -P 标记时,Docker 会随机映射一个 49000~49900 的端口到内部容器开放的网络端口。 (如果写了Dockerfile, 将会使用EXPOSE处来的端口)

使用 docker ps 可以看到,本地主机的 49155 被映射到了容器的 5000 端口。此时访问本机的 49155 端口即可访问容器内 web 应用提供的界面。

$ sudo docker run -d -P training/webapp python app.py
$ sudo docker ps -l
CONTAINER ID  IMAGE                   COMMAND       CREATED        STATUS        PORTS                    NAMES
bc533791f3f5  training/webapp:latest  python app.py 5 seconds ago  Up 2 seconds  0.0.0.0:49155->5000/tcp  nostalgic_morse

-p(小写的)则可以指定要映射的端口,并且,在一个指定端口上只可以绑定一个容器。支持的格式有 ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort。

例如:

映射所有接口地址

使用 hostPort:containerPort 格式本地的 5000 端口映射到容器的 5000 端口,可以执行

$ sudo docker run -d -p 5000:5000 training/webapp python app.py

此时默认会绑定本地所有接口上的所有地址。

二、容器互联

容器的连接(linking)系统是除了端口映射外,另一种跟容器中应用交互的方式。

该系统会在源和接收容器之间创建一个隧道,接收容器可以看到源容器指定的信息。

使用 --link 参数可以让容器之间安全的进行交互。

下面先创建一个新的数据库容器。

$ sudo docker run -d --name db training/postgres

然后创建一个 web 容器,并将它连接到 db 容器

$ sudo docker run -d -P --name web --link db:db training/webapp python app.py

此时,db 容器和 web 容器建立互联关系。

--link 参数的格式为 --link name:alias,其中 name 是要链接的容器的名称,alias 是这个连接的别名。

使用 docker ps 来查看容器的连接

$ docker ps
CONTAINER ID  IMAGE                     COMMAND               CREATED             STATUS             PORTS                    NAMES
349169744e49  training/postgres:latest  su postgres -c '/usr  About a minute ago  Up About a minute  5432/tcp                 db, web/db
aed84ee21bde  training/webapp:latest    python app.py         16 hours ago        Up 2 minutes       0.0.0.0:49154->5000/tcp  web

可以看到自定义命名的容器,db 和 web,db 容器的 names 列有 db 也有 web/db。这表示 web 容器链接到 db 容器,web 容器将被允许访问 db 容器的信息。

Docker 在两个互联的容器之间创建了一个安全隧道,而且不用映射它们的端口到宿主主机上。在启动 db 容器的时候并没有使用 -p 和 -P 标记,从而避免了暴露数据库端口到外部网络上。