0%

介绍

每一个使用linux或类unix系统的人都会用到的命令mount,它用来挂载/卸载文件系统。这是该命令基本功能,除了基本功能该命令还有其它功能。

更多多多多多多多多多多多多多多多用法

绑定挂载(bind mount)

windows的快捷方式、linux的软硬链接,相信你已很熟悉了,下面我来说说mount的绑定挂载--bind

linux的软硬链接原理:
原理图

  • 软连接受应用程序影响,不是所有应用程序都能通过软连接访问资源的
  • 硬链接只能作用在文件上,不能为文件夹创建硬链接

bind mount:
能成功克服软硬链接的缺点,将文件或文件夹挂载到指定的挂载点上。使对挂载点的操作转移到文件或文件夹上。

1
2
3
4
5
6
7
 # ls -li uts*
583819234 -rw-r--r--. 1 root root 4 1月 23 16:55 uts
606248334 -rw-r--r--. 1 root root 2 1月 23 10:44 uts2
# mount --bind ./uts2 ./uts
# ls -li uts*
606248334 -rw-r--r--. 1 root root 2 1月 23 10:44 uts
606248334 -rw-r--r--. 1 root root 2 1月 23 10:44 uts2

uts的inode变成了,uts2的inode id,所有访问uts的io都会转移到uts2上;并且在mount中能查看到bind mount信息。

1
2
3
 # mount
...
/dev/mapper/rhel_rhel82-root on /home/zhoub/uts type xfs (rw,relatime,seclabel,attr2,inode64,noquota)

bind mount是用挂载文件或目录信息遮盖了挂载的文件或目录,当不需要时,可以通过umount卸载,恢复原文件或目录的信息。

挂载传播(mount propagation)

执行clone()时,进程拷贝当前文件系统树,此后,新进程就拥有与原进程相同的文件系统树拷贝,两个文件系统树中的任何挂载操作都不会影响另一个拷贝。尽管每个进程使用单独的文件系统名称空间在理论上非常有意义,但完全隔离也会造成较大限制性。进程克隆了系统的文件系统名称空间之后,已经运行的系统守护进程无法为这个用户自动挂载 CD-ROM,因为在原文件系统名称空间中执行的挂载无法影响用户的拷贝。2006 年引入的挂载传播(mount propagation)解决了这个问题,挂载传播定义了挂载对象之间的关系。

  • 共享挂载(share mount)
    如果两个挂载对象具有共享关系,那么一个挂载对象中的挂载事件会传播到另一个挂载对象,反之亦然
    mount --make-shared <mount-object>
    mount --make-rshared <mount-object>
  • 从属挂载(slave mount)
    如果两个挂载对象形成从属(slave)关系,那么一个挂载对象中的挂载事件会传播到另一个挂载对象,但是反过来不行;在这种关系中,从属对象是事件的接收者
    mount --make-slave <mount-object>
  • 私有挂载(private mount)
    既不传播也不接收挂载事件
    mount --make-private <mount-object>
  • 不可绑定挂载(unbindable mount)
    与私有挂载相似,但是不允许执行绑定挂载
    mount --make-unbindable <mount-object>

参考&鸣谢

介绍

ceph为统一存储,包括块存储(rbd)、文件存储(cephfs)、对象存储(radosgw)。正常我们所使用rbd cli使用的是库librbd来操作ceph的。

版本

ceph-jewel

架构

librbd组织结构:
librbd

librbd架构:
librbd_frame

接口

*** todo ***

参考&鸣谢

*** None ***

介绍

新买了一个TF卡,Class10的,应该能挺快。原来的卡是class4的,如果用新卡做系统,然后所有软件都重装重配,太麻烦!所以将现有系统,做成镜像(img),然后直接dd到新卡上,然后再执行raspi-conf进行容量扩展就可以正常使用了。

版本

  • 硬件,RaspberryPi3
  • OS,raspbian jessie

步骤

安装需要的软件

安装dosfstoolspartedkpartx软件及依赖包。

1
sudo apt-get install dosfstools parted kpartx

计算镜像大小

获取/dev/root/dev/mmcblk0p1占用的空间,然后计算镜像大小,计算公式:1.2 * (size(/dev/root) + size(/dev/mmcblk0p1))

1
2
3
4
$ df -P
Filesystem 1024-blocks Used Available Capacity Mounted on
/dev/root 30690780 1390740 28024812 5% /
/dev/mmcblk0p1 64366 20698 43668 33% /boot

df返回的容量单位为KB。

生成空白镜像文件

使用dd/dev/zero生成空白镜像文件sudo dd if=/dev/zero of={镜像文件} bs=1K count={镜像大小(单位为K)}

分区镜像文件并格式化

分区

使用parted为img文件分区

1
2
3
4
5
6
7
8
# 标记label,选择MBR or GPT主引导格式,MBR:msdos, GPT:gpt
sudo parted {镜像文件} -s -- mklabel msdos

# 创建分区
sudo parted {镜像文件} -s -- mkpart primary fat32 {开始扇区}s {结束扇区}s

# 创建分区
sudo parted {镜像文件} -s -- mkpart primary ext4 {开始扇区}s -1

开始扇区和结束扇区可是通过sudo fdisk -l /dev/mmcblk0来查看。

格式化

在格式化前,需要先将img文件映射给系统的loop设备,然后在将loop设备映射给device mapper。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 映射loop设备
$ sudo losetup -f --show {镜像文件}
/dev/loop0

# device mapper
$ sudo kpartx -va /dev/loop0
add map loop0p1 (254:0): 0 257 linear /dev/loop0 256
add map loop0p2 (254:1): 0 18015 linear /dev/loop0 513

# 格式化
$ sudo mkfs.vfat /dev/mapper/loop0p1
......
$ sudo mkfs.ext4 /dev/mapper/loop0p2
......

挂载并备份系统

备份/boot

1
2
3
$ sudo mount -f vfat /dev/mapper/loop0p1 /media
$ sudo cp -rfp /boot/* /media/
$ sudo umount /media

备份/

1
2
3
4
$ sudo mount -t ext4 /dev/mapper/loop0p2 /media
$ sudo rsync -aP --exclude={镜像文件} --exclude=/media/* --exclude=/sys/* --exclude=/proc/* --exclude=/tmp/* / /media/
...
$ sudo umount /media

卸载打包镜像文件

1
2
3
4
5
# 关闭device mapper
sudo kpartx -d /dev/loop0

#关闭 loop 设备
sudo losetup -d /dev/loop0

参考&鸣谢

背景

文件系统,是任何OS都不可却少的。想要编写一个属于自己的文件系统很容易,但调试十分不方便。为了方便调试,提高开发效率,可以使用FUSE(Filesystem in userspace)框架进行开发。这是一个内核模块,能够让用户在用户空间实现文件系统并且挂载到某个目录,就像在内核实现的文件系统一样。使用 FUSE 有几个好处:一是因为在用户空间实现,开发和调试都比较方便;二是可以把一些常用的服务以文件系统的形式展现,方便操作,如 ftpfs,sshfs,mailfs 等;另外可以避免一些版权问题,如 Linux 上对 ntfs,zfs 的操作都是通过 FUSE 实现的。当然用户空间的实现也有缺点,最明显的就是由多次在用户态/内核态切换带来的性能下降。

FUSE架构

FUSE Frame
将文件系统需要处理的读、写、创建、删除等函数,以回调的方式注册到FUSE模块中,当用户访问挂载目录时,FUSE模块回调相应的注册接口。

FUSE安装

ubuntu 16.04 安装

需要安装libfuse2libfuse-dev

1
sudo apt-get install libfuse2 libfuse-dev pkg-config

版本:

1
2
3
FUSE library version: 2.9.4
fusermount version: 2.9.4
using FUSE kernel interface version 7.19

OSX 10.11.6 安装

需要安装Caskroom/cask/osxfuse

1
brew cask install osxfuse

版本:

1
2
3
OSXFUSE 3.5.4
FUSE library version: 2.9.7
fuse: no mount point

FUSE使用

FUSE有两种接口,一种是fuse_operations另一种是fuse_lowlevel_ops

  • fuse_operations是较为上层的接口,我们可以使用fuse_main函数将其传入FUSE中
    eg:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    static struct fuse_operations cryptfs_ops = {
    .init = cfs_init,
    .destroy = cfs_destroy,
    .open = cfs_open,
    .read = cfs_read,
    .write = cfs_write,
    .release = cfs_release,
    .readdir = cfs_readdir,
    .getattr = cfs_getattr,
    };
  • fuse_lowlevel_ops是较底层的接口,我们可以使用fuse_session_loop函数实现
    eg:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    static struct fuse_lowlevel_ops  lowlevel_handler =   
    {
    .lookup = lowlevel_lookup,
    .getattr = lowlevel_getattr,
    .readdir = lowlevel_readdir,
    .mkdir = lowlevel_mkdir,
    .rmdir = lowlevel_rmdir,
    .open = lowlevel_open,
    .read = lowlevel_read,
    .write = lowlevel_write,
    .unlink = lowlevel_unlink,
    .rename = lowlevel_rename,
    };
    fuse_operations使用简单,容易上手。fuse_lowlevel_ops灵活性大,需要有FS开发经验。

Helloworld示例

实现ls ./mp系统提示Hello-world(./mp为挂载目录)
ls会用到的方法代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <string.h>
#include <fuse.h>

static int cfs_readdir(const char* path, void* buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info* fi)
{
fprintf(prd->logfile, "cfs_readdir\t path : %s\n", path);
fflush(prd->logfile);

return filler(buf, "Hello-world", NULL, 0);
}

static int cfs_getattr(const char* path, struct stat *stbuf)
{
fprintf(prd->logfile, "cfs_getattr\t path : %s\n", path);
fflush(prd->logfile);
if(strcmp(path, "/") == 0)
stbuf->st_mode = 0755 | S_IFDIR;
else
stbuf->st_mode = 0644 | S_IFREG;
return 0;
}

fuse_main将接口注册到FUSE中:

1
2
3
4
5
6
7
8
9
10
11
static struct fuse_operations cryptfs_ops = {
.readdir = cfs_readdir,
.getattr = cfs_getattr,
};

int main(int argc, char *argv[])
{
int ret = 0;
ret = fuse_main(argc, argv, &cryptfs_ops);
return ret;
}

编译

ubuntu 16.04

编译命令:

1
gcc myfuse.c -o myfuse -DFUSE_USE_VERSION=22 `pkg-config fuse --cflags --libs` -g

or

1
gcc myfuse.c -o myfuse -DFUSE_USE_VERSION=22 -D_FILE_OFFSET_BITS=64 -I/usr/include/fuse -lfuse -pthread -g 

使用后者编译时不需要安装包pkg-config,由于不同操作系统fuse安装位置不一样,所以还是推荐使用pkg-config的方式

调试

可使用GDB调试,myfuse会以守护进程方式启动,调试有三种方法。

  • myfuse进程启动后,使用GDB中的attach方法进行调试
  • 使用GDB调试myfuse,参数中增加-d参数,该参数可以使myfuse不已守护进程方式启动 ( ** Version: 2.9.9上无法实现 ** )
  • myfuse中增加调试打印信息进行调试

挂载FUSE

先创建一个目录(mkdir ./mp),用于挂载myfuse文件系统,然后调用myfuse进行挂载。
eg:

1
sudo ./myfuse ./mp

对于mac系统挂载文件系统需要使用参数-o allow_other,否则挂在后,挂载目录将无权访问。
eg:

1
sudo ./myfuse ./mp -o allow_other

使用mount查看挂载的文件系统。
eg:

1
2
3
$ mount
...
/home/xxxx/xxxx/myfuse on /home/xxxx/xxxx/mp type fuse.myfuse (rw,nosuid,nodev,relatime,user_id=1000,group_id=1000)

使用ls ./mp查看挂载点内容,根据实现应提示Hello-world
eg:

1
2
$ ls ./mp/
Hello-world

卸载FUSE

使用umount卸载myfuse文件系统,需要输入挂载点的全路径
eg:

1
sudo umount /home/xxxx/xxxx/mp

FUSE Options说明

fuse_main函数自带usage信息,只需要将--help参数传入,便能将usage信息打出来。

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
$ ./myfuse --help
usage: ./myfuse mountpoint [options]

general options:
-o opt,[opt...] mount options
-h --help print help
-V --version print version

FUSE options:
-d -o debug enable debug output (implies -f)
-f foreground operation
-s disable multi-threaded operation

-o allow_other allow access to other users
-o allow_root allow access to root
-o auto_unmount auto unmount on process termination
-o nonempty allow mounts over non-empty file/dir
-o default_permissions enable permission checking by kernel
-o fsname=NAME set filesystem name
-o subtype=NAME set filesystem type
-o large_read issue large read requests (2.4 only)
-o max_read=N set maximum size of read requests

-o hard_remove immediate removal (don't hide files)
-o use_ino let filesystem set inode numbers
-o readdir_ino try to fill in d_ino in readdir
-o direct_io use direct I/O
-o kernel_cache cache files in kernel
-o [no]auto_cache enable caching based on modification times (off)
-o umask=M set file permissions (octal)
-o uid=N set file owner
-o gid=N set file group
-o entry_timeout=T cache timeout for names (1.0s)
-o negative_timeout=T cache timeout for deleted names (0.0s)
-o attr_timeout=T cache timeout for attributes (1.0s)
-o ac_attr_timeout=T auto cache timeout for attributes (attr_timeout)
-o noforget never forget cached inodes
-o remember=T remember cached inodes for T seconds (0s)
-o nopath don't supply path if not necessary
-o intr allow requests to be interrupted
-o intr_signal=NUM signal to send on interrupt (10)
-o modules=M1[:M2...] names of modules to push onto filesystem stack
-o max_write=N set maximum size of write requests
-o max_readahead=N set maximum readahead
-o max_background=N set number of maximum background requests
-o congestion_threshold=N set kernel's congestion threshold
-o async_read perform reads asynchronously (default)
-o sync_read perform reads synchronously
-o atomic_o_trunc enable atomic open+truncate support
-o big_writes enable larger than 4kB writes
-o no_remote_lock disable remote file locking
-o no_remote_flock disable remote file locking (BSD)
-o no_remote_posix_lock disable remove file locking (POSIX)
-o [no_]splice_write use splice to write to the fuse device
-o [no_]splice_move move data while splicing to the fuse device
-o [no_]splice_read use splice to read from the fuse device

FUSE Operations说明

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
struct fuse_operations {
int (*getattr) (const char *, struct stat *);
/* 这个函数与 stat() 类似。st_dev 和 st_blksize 域都可以忽略。st_ino 域也会被忽略,除非在执行 mount 时指定了 use_ino 选项 */

int (*readlink) (const char *, char *, size_t);
/* 这个函数会读取一个符号链接的目标。缓冲区应该是一个以 null 结束的字符串。缓冲区的大小参数包括这个 null 结束字符的空间。如果链接名太长,不能保存到缓冲区中,就应该被截断。成功时的返回值应该是 “0” */

int (*getdir) (const char *, fuse_dirh_t, fuse_dirfil_t);
/* 这个函数会读取一个目录中的内容。这个操作实际上是在一次调用中执行 opendir()、readdir()、...、closedir() 序列。对于每个目录项来说,都应该调用 filldir() 函数 */

int (*mknod) (const char *, mode_t, dev_t);
/* 这个函数会创建一个文件节点。此处没有 create() 操作;mknod() 会在创建非目录、非符号链接的节点时调用 */

int (*mkdir) (const char *, mode_t);
int (*rmdir) (const char *);
/* 这两个函数分别用来创建和删除一个目录 */

int (*unlink) (const char *);
int (*rename) (const char *, const char *);
/* 这两个函数分别用来删除和重命名一个文件 */

int (*symlink) (const char *, const char *);
/* 这个函数用来创建一个符号链接 */

int (*link) (const char *, const char *);
/* 这个函数创建一个到文件的硬链接 */

int (*chmod) (const char *, mode_t);
int (*chown) (const char *, uid_t, gid_t);
int (*truncate) (const char *, off_t);
int (*utime) (const char *, struct utimbuf *);
/* 这 4 个函数分别用来修改文件的权限位、属主和用户、大小以及文件的访问/修改时间 */

int (*open) (const char *, struct fuse_file_info *);
/* 这是文件的打开操作。对 open() 函数不能传递创建或截断标记(O_CREAT、O_EXCL、O_TRUNC)。这个函数应该检查是否允许执行给定的标记的操作。另外,open() 也可能在 fuse_file_info 结构中返回任意的文件句柄,这会传递给所有的文件操作 */
int (*read) (const char *, char *, size_t, off_t, struct fuse_file_info *);
/* 这个函数从一个打开文件中读取数据。除非碰到 EOF 或出现错误,否则 read() 应该返回所请求的字节数的数据;否则,其余数据都会被替换成 0。一个例外是在执行 mount 命令时指定了 direct_io 选项,在这种情况中 read() 系统调用的返回值会影响这个操作的返回值 */

int (*write) (const char *, const char *, size_t, off_t,struct fuse_file_info *);
/* 这个函数将数据写入一个打开的文件中。除非碰到 EOF 或出现错误,否则 write() 应该返回所请求的字节数的数据。一个例外是在执行 mount 命令时指定了 direct_io 选项(这于 read() 操作的情况类似) */

int (*statfs) (const char *, struct statfs *);
/* 这个函数获取文件系统的统计信息。f_type 和 f_fsid 域都会被忽略 */

int (*flush) (const char *, struct fuse_file_info *);
/* 这表示要刷新缓存数据。它并不等于 fsync() 函数 —— 也不是请求同步脏数据。每次对一个文件描述符执行 close() 函数时,都会调用 flush();因此如果文件系统希望在 close() 中返回写错误,并且这个文件已经缓存了脏数据,那么此处就是回写数据并返回错误的好地方。由于很多应用程序都会忽略 close() 错误,因此这通常用处不大 */

int (*release) (const char *, struct fuse_file_info *);
/* 这个函数释放一个打开文件。release() 是在对一个打开文件没有其他引用时调用的 —— 此时所有的文件描述符都会被关闭,所有的内存映射都会被取消。对于每个 open() 调用来说,都必须有一个使用完全相同标记和文件描述符的 release() 调用。对一个文件打开多次是可能的,在这种情况中只会考虑最后一次 release,然后就不能再对这个文件执行更多的读/写操作了。release 的返回值会被忽略 */

int (*fsync) (const char *, int, struct fuse_file_info *);
/* 这个函数用来同步文件内容。如果 datasync 参数为非 0,那么就只会刷新用户数据,而不会刷新元数据 */

int (*setxattr) (const char *, const char *, const char *, size_t, int);
int (*getxattr) (const char *, const char *, char *, size_t);
int (*listxattr) (const char *, char *, size_t);
int (*removexattr) (const char *, const char *);
/* 这些函数分别用来设置、获取、列出和删除扩展属性 */
......
};

更多详细说明,请见fuse.h文件。

参考&鸣谢

背景

VPN(virtual private network)虚拟专用网络,目前主要能接触到的协议包括PPTP(点对点隧道协议)和L2TP(2层隧道协议)

  • PPTP(2层VPN) 由微软和3Com等公司组成的PPTP论坛开发的点对点隧道协议,基于PPP协议使用PAP或CHAP之类的加密算法。
  • L2TP(2层VPN) 是IETF基于L2F (Cisco的第二层转发协议)开发的PPTP的后续版本,是一种工业标准 Internet 隧道协议,其可以为跨越面向数据包的媒体发送点到点协议 (PPP) 框架提供封装。
    PPTP和L2TP都使用PPP协议对数据进行封装,然后添加附加包头用于数据在互联网络上的传输。PPTP只能在两端点间建立单一隧道。 L2TP支持在两端点间使用多隧道,用户可以针对不同的服务质量创建不同的隧道。L2TP可以提供隧道验证,而PPTP则不支持隧道验证。但是当L2TP 或PPTP与IPSEC共同使用时,可以由IPSEC提供隧道验证,不需要在第2层协议上验证隧道使用L2TP。PPTP要求互联网络为IP网络。L2TP只要求隧道媒介提供面向数据包的点对点的连接,L2TP可以在IP(使用UDP),桢中继永久虚拟电路 (PVCs),X.25虚拟电路(VCs)或ATM VCs网络上使用。

IPSec(3层VPN),隧道模式隧道是封装、路由与解封装的整个过程。隧道将原始数据包隐藏(或封装)在新的数据包内部。该新的数据包可能会有新的寻址与路由信息,从而使其能够通过网络传输。隧道与数据保密性结合使用时,在网络上窃听通讯的人将无法获取原始数据包数据(以及原始的源和目标)。封装的数据包到达目的地后,会删除封装,原始数据包头用于将数据包路由到最终目的地。

搭建环境

  • OS, raspbian jessie
  • 硬件,Raspbarry Pi 3B
  • ppp,2.4.6
  • pptpd,1.4.0
  • openswan,2.6.38
  • xl2tp,1.3.6

PPTP部署

软件安装

安装ppppptpd

1
2
sudo apt-get install ppp
sudo apt-get install pptpd

配置pptpd

在配置文件/etc/pptpd.conf中,增加如下内容:

1
2
localip 192.168.1.101       # 本地ip
remoteip 192.168.1.110-120 # 设置ip分配范围

*** 注:ip地址不要有冲突 ***

配置ppp

在配置文件/etc/ppp/chap-secrets中,增加用户名密码
格式: {用户名}<tab>*<tab>{密码}<tab>*

在配置文件/etc/ppp/options中,设置MTU

1
2
-mru        # 关闭MRU协商
mru 1496 # 设置MTU值为1496

ip转发配置

如果需要通过VPN访问宿主机本地网络,需要开通ip转发功能,在/etc/sysctl.conf中设置net.ipv4.ip_forward=1。并执行sudo sysctl -p使修改生效。

重启pptpd服务

sudo systemctl restart pptpd

IPsec over L2TP部署

软件安装

安装pppxl2tpdopenswan

1
2
3
sudo apt-get install ppp
sudo apt-get install xl2tpd
sudo apt-get install openswan

IPsec配置及测试

配置

/etc/ipsec.conf中,增加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
onn L2TP-PSK-NAT
rightsubnet=vhost:%priv
also=L2TP-PSK-noNAT

conn L2TP-PSK-noNAT
authby=secret
pfs=no
auto=add
keyingtries=3
rekey=no
ikelifetime=8h
keylife=1h
type=transport
left=192.168.3.100 # 本机ip
leftprotoport=17/1701
right=%any
rightprotoport=17/%any

/etc/ipsec.secrets中增加预共享密钥{本地IP}<tab>%any: PSK<tab>"{密码}"
eg: 192.168.3.100 %any: PSK "lsjflsdkfj"

关闭内核ICMP重定向

1
2
3
4
5
for abspath in /proc/sys/net/ipv4/conf/*
do
echo 0 > $abspath/accept_redirects
echo 0 > $abspath/send_redirects
done

修改/etc/sysctl.conf中的配置

1
2
3
4
5
6
7
8
9
10
net.ipv4.ip_forward = 1
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.all.log_martians = 0
net.ipv4.conf.default.log_martians = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.icmp_ignore_bogus_error_responses = 1

执行sudo sysctl -p立即生效

测试

使用ipsec verify验证ipsec配置。在验证前,请先重启ipsec服务sudo systemctl restart ipsec

L2TP配置

/etc/xl2tpd/xl2tpd.conf中增加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[global]

listen-addr = 192.168.3.100
ipsec saref = yes

[lns default]
ip range = 192.168.3.120-192.168.3.123
local ip = 192.168.3.100
refuse chap = yes
refuse pap = yes
require authentication = yes
name = l2tp
ppp debug = yes

pppoptfile = /etc/ppp/options.xl2tpd

length bit = yes

PPP配置

options配置

新建xl2tpd专用options,并增加内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ipcp-accept-local
ipcp-accept-remote

#ms-dns 192.168.3.1
ms-dns 8.8.8.8
ms-dns 8.8.4.4

noccp
auth
crtscts
debug
hide-password
modem
lock
proxyarp

账户配置

可复用PPTP部署中的PPP配置的账户

重启L2TP服务

sudo systemctl restart xl2tpd

参考&鸣谢

linux的ip命令和ifconfig类似,ifconfig属于net-tools套件中,ip命令属于iproute2套件中。由于net-tools中的部分命令已经长时间没有更新维护了,所以iproute2的套件大有取代net-tools套件的趋势。

NET-TOOLS IPROUTE2对比

net-tools iproute2
arp -na ip neigh
ifconfig ip link
ifconfig -a ip addr show
ifconfig –help ip help
ifconfig -s ip -s link
ifconfig eth0 up ip link set eth0 up
ipmaddr ip maddr
iptunnel ip tunnel
netstat ss
netstat -i ip -s link
netstat -g ip maddr
netstat -l ss -l
netstat -r ip route
route add ip route add
route del ip route del
route -n ip route show
vconfig ip link

参考&鸣谢

背景

docker容器的创建依赖于镜像,镜像的创建依赖于目录树,不同的linux发行版,有自己的目录树。本文主要介绍debian族发行版与fedora族发行版base image的创建。如果您想从零创建一个linux发行版base image,请参考LFS(linux from scratch)。

测试环境

  • OS Version, RHEL7.2
  • docker Version, 1.12.5

debian

debian类的操作系统包括debian、ubuntu,这一类操作系统的目录树都是用debootstrap来生成的。其中de取debian之意。

安装

可直接使用yum安装yum install debootstrap

使用

debian默认的mirror为https://deb.debian.org/debian/

debootstrap格式:
debootstrap [OPTION...] <版本> <目录树存放位置目录> [<镜像(mirror)位置>]

eg: sudo debootstrap jessie my_jessie

fedora

fedora类的操作系统包括fedora、RHEL、CentOS,这一类操作系统的目录树都是用febootstrap来生成的。其中fe取fedora之意。

安装

febootstrap并没有存在于RHEL7.2(或CentOS7.2)Repo中,目前可以直接安装6.8 repo中的febootstrap-3.21-4

febootstrap-3.21-4依赖包如下所示:

  • fakechroot-2.9-24.5.el6_1.1.x86_64
  • fakechroot-libs-2.9-24.5.el6_1.1.x86_64
  • fakeroot-1.12.2-22.2.el6.x86_64
  • fakeroot-libs-1.12.2-22.2.el6.x86_64
  • febootstrap-supermin-helper-3.21-4.el6.x86_64

使用

febootstrap格式:
febootstrap [OPTION...] <版本> <目录树存放位置目录> [镜像(mirror)位置]

eg: febootstrap -i bash 6.8 my_centos6.8 http://mirrors.163.com/centos/6.8/os/x86_64/

LFS

Todo…

镜像导入

Tar导入方式创建镜像

无论是debootstrap还是febootstrap生成的目录,其导入docker image的方法是一样的。

格式:
tar -C <目录树目录> -c . | docker import - <docker image 名称>

eg: tar -C ./my_jessie -c . | docker import - my_jessie_base

空镜像创建

格式:
tar cv --files-from /dev/null | docker import - <docker image 名称>

eg: tar cv --files-from /dev/null | docker import - empty_image

参考&鸣谢

介绍

Raspberrypi3自带wifi模块,用户可以通过配置用户名密码就可以建立wifi连接。

版本

  • 硬件,RaspberryPi3
  • OS,raspbian jessie

配置

使用wpa_passphrase <SSID> <用户名> <密码>生成wifi连接需要的用户信息
然后,将wpa_passphrase生成的信息,追加到/etc/wpa_supplicant/wpa_supplicant.conf
最后重启系统。

参考&鸣谢

介绍

Raspberrypi原产至英国,其语言及键盘布局都是GB版的。而我们目前接触到的都是US版的。所以这里需要对键盘布局进行切换一下。

版本

  • 硬件,RaspberryPi3
  • OS,raspbian jessie

键盘配置

在命令行中输入sudo raspi-config,进入Internationaliation Options,选择Change Keyboard Layout,弹出键盘配置界面。

然后选择Generic 101-key PC,在layout中选择Other,然后在Country of origin for the keyboard中选择English (US),再在Keyboard Layout页面中选择English (US, alternative international)

参考&鸣谢

介绍

Dockerfile是docker image构建脚本,是用于生成image。Dockerfile忽略大小写,使用‘#’注释,每行只执行一条指令。Dockerfile指令分为两种,构建指令和设置指令。

  • 构建指令,用于构建image,不会在image的容器上执行
  • 设置指令,用于设置image属性,会在image的容器上执行

Dockerfile指令

FROM

构建指令,用于指定基础image。该指令必须指定在Dockerfile其它指令的前面,后面的Dockerfile指令都依赖于该指令指定的image。FROM指令指定的基础image可以是官方docker hub仓库中的,也可以是位于本地仓库中的。

指令格式:FROM <image>[:<tag>]
若不指定:<tag>,默认使用:latest

MAINTAINER

构建指令,用于将image的制作者相关信息写入到image中。

指令格式:MAINTAINER <name>

RUN

构建指令,可以运行任何被基础image支持的命令。

指令格式:RUN <command>RUN ["executable","param 1","param n"]
一般用于软件安装

CMD

设置指令,用于container启动时指定的操作。该操作可以是执行自定义脚本,也可以是执行系统命令。该指令只能在文件中存在一次,如果有多个,则只执行最后一条。

指令格式:CMD <executable> <param 1> <param n>CMD ["executable", "param 1", "param n"]

ENTRYPOINT

设置指令,指定容器启动时执行的命令,可以多次设置,但是只有最后一个有效。

指令格式:ENTRYPOINT ["executable", "param 1", "param n"]ENTRYPOINT <executable> <param 1> <param n>
CMD指令和ENTRYPOINT会互相覆盖只有最后一个CMD或者ENTRYPOINT有效。

和CMD指令配合使用来指定ENTRYPOINT的默认参数,这时CMD指令不是一个完整的可执行命令,仅仅是参数部分。ENTRYPOINT指令只能使用JSON方式指定执行命令,而不能指定参数。

1
2
3
FROM centos7
CMD ["-l"]
ENTRYPOINT ["/usr/bin/ls"]

USER

设置指令,设置启动容器的用户,默认是root用户

指令格式:USER <user name>

EXPOSE

设置指令,该指令会将容器中的端口映射成宿主机器中的某个端口。需要访问容器的时候,可以不是用容器的IP地址而是使用宿主机器的IP地址和映射后的端口。首先在Dockerfile使用EXPOSE设置需要映射的容器端口,然后在运行容器的时候指定-p选项加上EXPOSE设置的端口,这样EXPOSE设置的端口号会被随机映射成宿主机器中的一个端口号。也可以指定需要映射到宿主机器的那个端口,这时要确保宿主机器上的端口号没有被使用。EXPOSE指令可以一次设置多个端口号,相应的运行容器的时候,可以配套的多次使用-p选项。

指令格式:EXPOSE <port> [<port>...]

ENV

构建指令,在image中设置一个环境变量

指令格式:ENV <key> <value>
设置了后,后续的RUN命令都可以使用,container启动后,可以通过docker inspect查看这个环境变量,也可以通过在docker run –env key=value时设置或修改环境变量。

ADD

构建指令,从src复制文件到container的dest路径。

指令格式:ADD <src> <dest>

  • <src>是相对被构建的源目录的相对路径,可以是文件或目录的路径,也可以是一个远程的文件url
  • <dest>是container中的绝对路径
    所有拷贝到container中的文件和文件夹权限为0755,uid和gid为0;如果是一个目录,那么会将该目录下的所有文件添加到container中,不包括目录;如果文件是可识别的压缩格式,则docker会帮忙解压缩(注意压缩格式);如果<src>是文件且<dest>中不使用斜杠结束,则会将<dest>视为文件,<src>的内容会写入<dest>;如果<src>是文件且<dest>中使用斜杠结束,则会<src>文件拷贝到<dest>目录下。

VOLUME

设置指令,指定挂载点。使容器中的一个目录具有持久化存储数据的功能,该目录可以被容器本身使用,也可以共享给其他容器使用。我们知道容器使用的是AUFS,这种文件系统不能持久化数据,当容器关闭后,所有的更改都会丢失。当容器中的应用有持久化数据的需求时可以在Dockerfile中使用该指令。

指令格式:VOLUME ["<mount point path>"]

WORKDIR

设置指令,切换目录,可多次切换(相当于cd命令),对RUNCMDENTRYPOINT生效

指令格式:WORKDIR <path>

ONBUILD

设置指令,在子镜像中执行,在构建镜像时并不执行,而是在它的子镜像中执行

指令格式:ONBUILD <Dockerfile指令>

示例

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
# 第一条有效指令必须指出基础镜像
FROM my_ubuntu_03:latest

# 作者
MAINTAINER zhou bo

# 环境变量
ENV BASE_IMAGE="ubuntu:16"
ENV CURRENT_IMAGE="Simple Image"

# 安装 ssh server 端程序
RUN apt-get update
RUN apt-get install -y openssh-server

# 映射端口22
EXPOSE 22

# 启动 ssh 守护进程
CMD ["-D"]
ENTRYPOINT ["/usr/sbin/sshd"]

# 在 / 目录下挂在一个 DataFolder 目录
VOLUME ["/DataFolder"]

# 在子镜像中执行的指令
ONBUILD ENV SON_IAMGE="你永远是我儿子"

参考&鸣谢