使用Cobbler配置PXE服务器

之前我介绍过如何手动配置一个PXE服务器,但是手动配置PXE服务器步骤太过繁琐,而且有太多配置文件需要设置,很容易发生遗漏。幸好已经有大神帮我们解决了这个问题(我本来想写一个简单的Web代理程序,之前我在github上寻找过这样的程序,但是没有找到)。本篇文章我将尝试使用Cobbler解决手动配置PXE服务器的麻烦问题,Cobbler是一个PXE管理程序,可以用来管理镜像和kickstart配置文件等。 Cobbler 最初由 Michael DeHaan 编写,现在项目主导人是 James Cammarata, 他目前在 Ansible 公司工作。(所以我们后面还会介绍ansible的使用。)

本文使用的是CentOS 7系统,依照Cobbler官网的 quick start 进行部署,不同的系统会有少许区别,Cobbler对 red hat系的系统更加友好。

首先安装cobbler, CentOS7 原生库中没有Cobbler,可以安装epel-release库。

yum install -y epel-release

然后安装cobbler

yum install -y cobbler

请留意虽然cobbler会帮助安装tftp和httpd,但是并不会帮助安装dhcp,所以安装完成cobbler后还需要手动安装dhcp。

yum install -y dhcp

安装完成后需要进行一些必要的配置,配置文件是/etc/cobbler/settings,使用yaml格式。

1.部署后的默认密码,default_password_crypted:”$1$bfI7WLZz$PxXetL97LkScqJFxnW7KS1″

这里配置的密码是加密后的密码,你可以使用openssl passwd -1输入密码并得到加密后的密码,然后将得到的密码配置到这里。

2.server和next_server地址,server地址指的是cobbler server使用的地址,next_server地址指的是DHCP/PXE和TFTP服务器使用的地址。

3.DHCP管理和DHCP服务器模板

通过修改manage_dhcp设置是否由cobbler管理dhcp服务器,cobbler是通过生成dhcpd.conf管理dhcp服务器的,所以我们需要自行安装dhcp服务。

dhcpd.conf依据模板生成,模板文件是/etc/cobbler/dhcp.template,我们可以自定义template的内容。

cobbler将镜像保存在/var/www/cobbler/ks_mirror目录中,所以对/var目录的空间依赖很重,请确保/var目录有足够的空间。

启动Cobbler和设置自启动

使用命令

systemctl start cobblerd.service启动cobbler服务,

使用

systemctl enable cobblerd.service设置cobbler开机启动。

使用

systemctl status cobblerd.service查看cobbler服务状态。

启动cobblerd服务之前首先启动httpd服务,否则使用cobbler check检查时会报错。

systemctl start httpd

然后执行systemctl start cobblerd

cobbler check会为配置pxe服务器提供进一步的建议,你需要依照建议依次解决这些问题。处理完成后重启cobblerd服务。

systemctl restart cobblerd

反复检查并解决后,使用cobbler sync命令初始化cobbler服务。

看到任务完成后cobbler的部署就算完成了,但是不要忘了允许TFTP和HTTP通过防火墙,如果你是用firewalld防火墙,允许他们通过的操作相对简单。

firewall-cmd --add-service=tftp --permanent
firewall-cmd --add-service=http --permanent
firewall-cmd reload

当然也不要忘了保证dhcpd tftp httpd 的正常运行。

下面我们需要添加镜像,来使用cobbler。

首先下载需要的镜像,比如我是用CentOS 7 minimal镜像

将镜像挂载到合适的目录

mount -t iso9660 -o loop,ro CentOS-7-x86_64-Minimal-1708.iso /mnt                                                                                                                                                                                                                                                                                                       

然后使用cobbler import 命令添加镜像。

cobbler import --name=CentOS7 --arch=x86_64 --path=/mnt

添加完成会可以使用cobbler distro list 查看所有发行版,使用cobbler profile list 查看所有描述。

更详细的报告使用cobbler distro report –name=CentOS7-x86_64查看。

现在我们已经可以使用PXE启动了。

但是创建system object 可以使用cobbler更多的特征。使用命令

cobbler system add –name=test –profile=CentOS7-x86_64创建systemobject。

使用cobbler system list查看列表。

使用system 可以配置更多的信息。我现在还没有用到就不做介绍了,更多内容可以到cobbler官网查看。

使用firewalld设置NAT

之前配置的PXE服务器虽然已经可用,但是一直是将PXE服务器所在的机器作为子网的一台设备使用,其他客户机要上网还需要另外配置路由器,这样布置起来整个网络很复杂。所以干脆利用PXE服务器的双网卡设置NAT,让PXE服务器同时承担起网关的作用。

我搭建PXE服务器的时候使用的是CentOS7系统,使用CentOS7默认的防火墙搭建NAT非常简单方便,不需要设置内核参数等操作。我在网上找相关的教程找了好久都是既设置防火墙又调整内核参数,我觉的这样的操作很明显不优雅,不像是正确的操作。本文参照CSDN的一篇文章操作,全程只用到了firewalld。

首先我们要知道firewalld是按照zone来管理网络的,将网口添加到不同的zone下就会使用不同的规则,firewalld自带的两个zone:external和internal就是为NAT设置的,我们只需要将连接WAN的网口添加到external下,将连接LAN的网口添加到internal下就可以完成NAT的设置。我这里连接WAN的网口是enp3s0,连接LAN的网口是enp4s0。

使用命令

firewall-cmd --change-interface=enp3s0 --zone=external --permanent

将enp3s0添加到external zone下,命令中–permanent参数是设置永久配置。这个设置不会立即生效,需要重新使用命令 firewall-cmd –reload重新载入。如果不加–permanent设置会立刻生效,但是firewalld重启后会失效。

然后使用命令

firewall-cmd --change-interface=enp4s0 --zone=external --permanent

将enp4s0添加到internal zone下,同样使用firewall-cmd –reload,然后我们就可以将enp4s0的地址设置为子网网关,子网的设备就可以通过向enp4s0发送请求访问外网了。

请留意如果你之前public设置了自定义的配置,需要在internal重新配置。external zone默认也是允许ssh通过的,如果你不希望外网使用ssh进入网关,需要删除这个服务。

解决Windows和Linux双系统时间冲突的办法

对于使用双系统的小伙伴,从linux切换回windows会发现windows显示的时间会出现问题,这是因为windows将BIOS中保存的硬件时间当作本地时间来看待,而linux将BIOS时间作为UTC时间来看待。

这就会导致切换会Windows后时间会差你所在时区的量,解决这个问题有两个办法,一个办法是让Windows认为硬件时间是UTC时间,另一个办法是让linux认为硬件时间是本地时间。

我觉得全世界应该使用统一的时间戳,这样全世界在同一时刻的时间戳应该是一样的,和位置无关,而且可以直接通过比较时间戳的大小判断时间的先后,这样会避免很多麻烦,只有在你需要将时间呈现出来的时候才会根据时区转换出正确的时间。

所以在我看来最好的办法就是让Windows认为硬件时间是UTC时间,设置的方法是在注册表中添加一个RealTimeIsUniversal的变量。

首先在开始搜索regedit,或者使用Win+R运行regedit,找到\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation路径,在路径中添加一个变量为RealTimeIsUniversal的DWORD变量,然后将值设置为1.

添加RealTimeIsUniversal变量
设置RealTimeIsUniversal值为1

另外一种方法是设置linux将硬件时间当作本地时间,但是我不想用,也不想介绍。

UEFI是什么,有什么用?如何使用UEFI方式安装系统?

UEFI直白的讲是一种系统启动方式,但是这样讲实际上是不严格的。UEFI的全称是统一可扩展固件接口(Unified Extensible Firmware Interface),这就表示实际上它是一系列统一的接口标准,但是我们通常更关注它对系统引导的影响,我们通常拿它和BIOS方式做对比,本片文章也主要讨论UEFI对于启动的影响。

我们在配置启动选项的时候通常看到的是UEFI和Legacy两种方式,Legacy也就是传统BIOS对应的启动方式。说是启动方式,实际上说成“发现启动的方式”更加好理解。因为在启动过程中UEFI和Legacy主要功能是发现磁盘上可以启动的选项。

现在我们就来看看Legacy方式和UEFI方式分别是怎么发现启动的。在早期,系统的启动项是写在主引导记录也就是MBR中的,这个记录只能写在磁盘开头的446字节中,很明显主引导记录的容量和位置都有很大的局限性,并且磁盘的分区信息也是写在主引导记录中的,主引导记录的容量限制就更加明显了。也正是因为主引导记录的体积限制,我们只能用它创建4个分区的描述,这也是广为流传的一个硬盘只能创建4个主分区的原因。因为主引导记录的特点,电脑在开机的时候只需要完成一定工作后读取主引导记录并按照主引导记录的描述继续读取需要运行的程序就可以了,但是如果主引导记录损坏,那整个磁盘就都会遇到故障,这也是使用Legacy经常遇到系统无法启动的原因。

而对应UEFI的引导方式不再继续使用MBR分区方式,而是采用GPT分区表。GPT分区表可以带来更多的分区和更大的分区容量。虽然很多人用不上更多的分区和更大的磁盘,但我依然推荐使用GPT分区方式。实际上GPT分区表里是有MBR的,但是包含MBR记录的目的仅仅是告诉那些只支持MBR的电脑不要乱动我的分区表。在使用GPT分区表的情况下,机器不能仅仅通过读取第一个扇区就确定可以从哪里启动了,但是不用担心因为UEFI比BIOS更加强大,它不仅能轻松发现哪些分区可以启动,甚至可以直接挂载分区进行文件管理。实际上UEFI发现启动选项正式依靠文件管理,UEFI可以直接读取各个分区的文件搜索可以启动的文件,如果你的机器是使用UEFI方式引导的,你通常可以在启动分区中发现一个叫做EFI的文件夹,这里面就是引导要用到的程序。如果你确定你的电脑是UEFI方式引导的,但是却看不到EFI文件,那是因为有一个专门的EFI分区,并且这个分区通常是没有设置卷标或者挂载出来。使用这个方式发现引导的好处很明显,首先避免的主引导记录的位置和容量的限制,所有分区的任意位置都可以存储引导信息和引导程序,其次,如果你的硬盘头部的扇区损坏了,你完全可以不用他们,只需要在分区的时候跳过它们,不管你的分区在哪个位置,都可以通过写入EFI文件配置启动。其次得益于UEFI的文件管理功能,有些高级的主板甚至支持直接挂载分区对分区中的文件进行编辑,这样在发生引导错误的时候我们就可以直接通过覆写文件的方式进行修复,实际上Windows10上的自动修复修复功能就是这样实现的(但是确实更加复杂),相比传统的编辑磁盘头部的主引导记录,这种方式真避免了很多麻烦。

那么如何设置UEFI启动呢?配置UEFI启动需要在安装系统的时候配置。首先你要确定你的机器支持UEFI模式启动,只要你的机器不是古董机通常都可以使用UEFI方式启动,你可以在BIOS(你注意到我这里使用BIOS指代UEFI和传统BIOS,实际上UEFI就是一种BIOS)选项中查看引导选项,通常有Legacy、UEFI和兼容3种模式,如果你确定你不在需要使用Legacy启动方式,你也可以直接设置为仅UEFI模式。

然后需要确定你要安装的操作系统是否支持UEFI模式启动。方法很简单,使用虚拟磁盘挂载安装镜像或者使用解压缩软件打开镜像,查看镜像中有没有EFI文件夹。如果存在EFI文件夹,那就说明这个镜像支持以UEFI的方式启动。

下面我们就来创建一个可以使用UEFI模式启动的U盘,使用Legacy方式启动的启动U盘通常需要使用烧录软件模拟成CD-ROM进行创建,相比之下,创建UEFI模式启动的U盘就简单多了。首先将U盘格式化为FAT32格式,如果你的U盘已经是FAT32格式就不需要再进行格式化了,然后使用虚拟光驱挂载镜像,将所有文件拷贝的U盘中,或者直接使用解压缩工具解压到U盘中。得益于UEFI模式使用文件管理发现启动项的方式,只要使用主板上的UEFI程序支持的文件系统并将所有安装文件拷贝到分区中就可以创建一个引导分区。同理,使用任何主板支持的存储介质都可以创建一个安装盘。

值得之前没有使用UEFI安装系统的同学注意,在使用UEFI安装系统的时候要选择有UEFI前缀的引导项,如果你找不到这个引导项,需要到BIOS中确认是否支持或者打开UEFI引导方式。

希望通过上文,你可以明白UEFI并没有多么复杂,相比之下使用UEFI来配置计算机更加简单,并且可以带来更多的好处。甚至有些厂商会在UEFI中嵌入反病毒程序,这也都得益于UEFI文件管理等新特点。至于BIOS和UEFI在运行中的区别,我们可以理解成传统BIOS只知道读取磁盘的第一条记录,然后按照他的描述去启动,而UEFI不一样,UEFI清楚的知道自己启动的是什么以及如何启动它,甚至还可以对启动项目安排一下,总之更高级就是了。

Golang 错误声明的几种方式

golang的错误处理机制非常特别,golang将错误声明为一种接口,并且可以作为参数传递。这种机制是为了让程序员重视错误处理,同时避免胡乱引发异常的情况。

在使用golang返回错误的时候,我通常使用以下几种方式:

1.直接返回声明的错误:

示例:

return errors.New("这是一个错误!")

return fmt.Errorf("这是一个错误!")

这种方式使用起来是最快捷的,同时也是我最不推荐的。因为这种方式上级函数拿到错误之后很难分析到底发生了什么错误,即使可以通过比对字符串进行分析,也是既低效又不稳定的方法。

2.返回声明的错误变量

示例:

var ErrMyErr  error = errors.New("这是一个错误!")

fun returnErr() err {
    return ErrMyErr
}

这种方式是我最推荐的方式,因为这是声明最方便,同时也是判断最简单的方案。上级函数拿到错误之后只需要简单的使用 == 运算就可以判断错误类型。

if err == ErrMyErr {
    // 进行错误处理
}

3.声明一种错误结构体:

示例:

type ErrMyErr struct {
    ErrString string
}

func (e ErrMyErr) Error() string {
    return fmt.Sprintf("字符串%s发生了错误", e.ErrString)
}

// 返回错误的函数
func returnErr()  error {
    return ErrMyErr{"发生错误的字符串"}
}

如果你的错误比较简单,只需要返回一个错误变量就可以让上级函数轻松的判断具体发生了什么错误,但是如果你的错误比较复杂,有一些参数需要跟随错误上报,那么简单的变量就不能满足这种情况了,我们需要先声明一种错误结构体,表示一类错误,然后声明独特的错误实例。这种情况适用于这一类错误每次发生的情况都会有一些小的区别的时候,比如查询数据库中的行的时候,某一列不存在,需要通过返回错误告知。

这种情况下判断错误类型也很简单:

if _, ok := err.(ErrMyErr); ok {
    // 错误处理
}

4.声明一种错误结构体,同时通过属性来区别错误。

示例:

type ErrMyErr struct {
    ErrString string
    ErrType uint
}

func (e ErrMyErr) Error() string {
    return fmt.Sprintf("字符串%s发生了错误", e.ErrString)
}

const (
    ErrTypezero    uint =  iota
    ErrTypeone
    //....
)

// 返回错误的函数
func returnErr() error {
    return ErrMyErr{"发生错误的字符串", ErrTypezero}
}

这种返回错误的方式就比较复杂了,幸亏它的适用场景也很少,大多数情况下,最好不要使用这种返回错误的方式。这种错误的定义方式适用于那些存在很多种类的错误,同时又是一大类错误的时候,有的时候我们只需要知道发生的错误是不是这一个大类,有的时候我们又希望知道具体发生的错误是哪个小类。当然我们也可以继续使用上一种错误声明方式,但是可能又要面临第一中错误声明方式遇到的比对字符串的情况。所以不如直接在结构体中定义一个属性用于说明错误的小类。

判断方法:

if _, ok := err.(ErrMyErr); ok {
    // 错误处理
}
if e, ok := err.(ErrMyErr); ok && e.ErrType == ErrTypezero {
    // 错误处理
}

5.使用普通的变量代表错误

以上几种方式都是围绕golang的错误处理机制实现的,但是其实仔细一想,这种错误处理机制并不受限于golang的语法,只是一种大家共同认可的方式,如果你自己简单的声明一个变量代表一种错误,同样可以实现返回错误。但是为了代码的兼容性,我也完全不赞成在错误处理的时候使用这种方式,除非你的错误非常小,或者算不上什么错误,别人根本不会在乎,也完全不需要实现Error()函数。

示例:

func returnErr() int {
    return 0
}

这几种方式是我能想到的几种声明错误的方式,我推荐先在包中定义错误变量,然后直接返回错误变量,这种方式几乎适用于绝大多数的情况,而且处理起来非常方便稳定。为了代码的兼容性和可维护性,希望看到这篇文章的人也都能养成良好的错误声明的习惯。

扩展Linux根目录分区

扩展用lvm方式创建的分区是非常简单的操作,但是因为之前没有操作过,简单的记录一下。

我不喜欢只有步骤的文章,但是因为是简单记录我又不想在这里写太多的理论知识,所以在看这篇文章之前,我希望你最好知道什么是lvm,也清楚lvm一些关键的词汇。

首先我们要知道,调整物理分区的大小是很麻烦的,所以调整虚拟分区的大小实际上是通过增加物理分区实现的。

之前用来做实验的虚拟机觉得不会用太多存储空间,就只添加了一个8G的硬盘,在根目录空间紧张的情况下,需要在虚拟机上扩容这块虚拟磁盘。新增的空间不能直接使用,需要到虚拟机中将根目录扩容才可以。

开机后可以看到,sda的空间已经扩大到20G了,需要通过添加分区将额外的12G利用起来。

简单的可以使用fdisk指令添加分区,创建新的主分区就可以了。选择分区编号和大小那里我都是选择默认的。

有些文章提到要使用t指令修改分区类型,我觉的其实是没有必要的,后面的pvcreate指令会替我们做这件事情的,但是我也是第一次做扩容的操作,所以还没有实验。

然后我们要使用这个分区创建pv, 使用命令 pvcreate [分区]。

现在将新创建的pv添加到vg中,使用命令vgextend [vg] [pv]。

最后扩展根目录的lv,使用命令 lvextend -l [pe 数量] [lv]。

扩展lv最好使用按照PE数量操作。

现在我用的/dev/centos/root这个虚拟分区的体积已经增大了,但是df -h查看一下。

根分区的大小并没有变化,因为这里显示的是文件系统的格式话后的分区大小,我们还需要扩展文件系统分区的大小。

最后的最后,使用xfs_growfs 指令扩展文件系统大小。

刚开始,我看的教程使用的是resize2fs指令,我试了几遍也没有用,然后我反应过来应该是文件系统不同,查询了一下,果然resize2fs指令是用来调整ext系列格式分区的。

 

Windows Server 2008 R2 创建 soft Raid5

很多人觉得用Windows创建软Raid的情况很少,但实际上Windows的软Raid软件已经很完善了,只是很少有人提,而且用Windows创建软Raid确实很简单,很方便。

当然我已经不推荐使用2008这种老版本的系统了,但是正好有客户询问,所以录制了一段简单的视频。

Node.js 学习笔记

因为之前对Node.js有一定的了解,所以很多基础已经有了,很多简单的入门的东西不会放在这里,更多的是一些自己原来的疑惑的梳理。

Node.js类的定义:

因为Node.js是弱类型的语言,相比其他弱类型语言,可以说是“将一切是对象”发挥到了极致,并且关键字里面也没有class,所以严格上说Node.js并没有定义类的机制。

Node.js 中的类都声明为函数,通过new 关键字创建对象。

function Hello() {
  this.name = '';
  this.set = function(name) {
    this.name = name;
  }
  this.get = function() {
    return this.name;
  }
}

创建实例

hello = new Hello();

除了标准的定义方法我们还可以通过定义一个返回一个对象的函数实现。

我推荐用函数创建一个完整的对象并返回

function Hello() {
hello = {};
hello.set = function(name) {
this.name = name;
};
hello.get = function() {
return this.name;
};
return hello;
}

通过 hello = Hello(); 的方式实例化对象。

另外一种方式是利用闭包实现的

function Hello() {
var name = '';
function setName(newName) {
name = newName;
}
function getName() {
return name;
}
return {
set: setName,
get: getName,
};
}

同样通过 hello = Hello(); 的方式实例化对象。

第二种方式有一个问题就是 hello.name 并不是set函数和get函数操作的那个name,而且新定义hello的函数也没法直接操作set函数和get函数中操作的name,这就是闭包的特点,如果你想保护什么变量,当然可以采用这种方式,不过这种方式实在太繁琐了,而且新手很难理解。
其实Node.js定义类的方式类似与其他语言的构造函数,即使是静态语言,创建实例化对象都是通过执行构造函数实现的,只不过有些语言的构造函数是隐式声明和执行的罢了。
所以我们也可以在构造函数中加一些判断,以适应不同的情况。
比如:

function Hello(name='', way='') {
if (name!='') {
this.name = name;
this.greet = function () {
console.log('hello ' + name);
};
} else {
if (way == '') {
this.way = 'wave';
} else {
this.way = way;
}
this.greet = function () {
console.log(way + ' to everyone!');
};
}
}

参数传递顺序:

继上面的例子

function Hello(name='', way='') {
if (name!='') {
this.name = name;
this.greet = function () {
console.log('hello ' + name);
};
} else {
if (way == '') {
this.way = 'wave';
} else {
this.way = way;
}
this.greet = function () {
console.log(way + ' to everyone!');
};
}
}

如果我们示例化对象的时候使用

shakeman =new Hello(name=”, way=’shake’);

bowman = new Hello(way=’bow’, name=”);

结果一样不一样呢?

结果是不一样的,通过这个例子我发现Node.js并不是通过参数的左值进行确定值赋给谁,而是按照传参的顺序确定的。

前者定义的对象是

Hello { way: ‘shake’, greet: [Function] }

后者定义的对象是

Hello { name: ‘bow’, greet: [Function] }

用过python的人看到这里可能会觉得难受了。

双引号和单引号有没有区别:

几乎所有文档里都说单引号和双引号功能上没有区别,但是我在生成query语句的时候却发现并不相同。

比如

var query =’select * from test where name =”my name”‘.;

打印出的语句是

select * from test where name =”my name”。

var query =”select * from test where name =’my name'”;

打印出来却是

select * from test where name =\’my name\’

也就是说使用双引号,node会假设创建的字符串是原始字符串,并对需要转义的字符使用转义字符。

当然,这只是我测试的结果,我还没有找到证据证明我的这个观点。

restify API框架 学习笔记

终止处理链:

res.send(false)

返回错误:

res.send(err)

返回错误,错误码来自于:

err.statusCode

停止服务器:

next.ifError(err)

使用正则表达式设置路由:

server.get(/^\/([a-zA-Z0-9_\.~-]+)\/(.*)/, function(req, res, next) {

// params获取参数列表
  console.log(req.params[0]);
  console.log(req.params[1]);
  res.send(200);
  return next();
});

 

 

配置syslinux菜单界面

CentOS 7的安装引导菜单简洁美观,但是我们制作的pxe启动却需要使用指令引导,这真的算不上优雅。所以我们现在来制作一个启动引导菜单。

本文参考syslinux官网config文档操作,编辑菜单的详细参数可以参考menu文档。设计布局比较复杂,所以我们就复制CentOS7的布局到我们的配置文件中就可以了。

首先需要找到CentOS7安装光盘中的syslinux配置文件。挂载光盘到/media目录,syslinux的配置文件就在/media/isolinux中,打开isolinux.cfg。内容如下:

我们只需要参考这个文件编辑我们放在/var/lib/tftpboot/pxelinux/pxelinux.cfg/目录下的default就可以实现一个和CentOS7安装引导目录一样美观引导菜单。

注意,如果你的/var/lib/tftpboot/pxelinux目录下没有vesamenu.c32,你可以拷贝/media/isolinux目录下的vesamenu.c32文件到该目录下。

然后配置default文件如下,详细各参数是什么一次请参考syslinux官网文档,各标签中append的参数可以参考红帽的文档中的引导选项章节

然后开启PXE客户机,因为我没有使用背景图片,所以引导菜单看起来是下面的样子,如果你觉得很丑可以使用背景图片。