解决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
}

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

6.最近我在阅读别人的代码时发现了另外一种定义错误的方式,就是把字符串重定义为错误类型,然后直接将错误描述的字符串强制类型转换为错误类型返回。

type Err String
func (e Err) Error() string{return string(e)}
func returnErr() error {
    return Err{"这里发生了一个错误"}
}

你也许会觉得,这和第一种方式没有什么区别,但是实际上相比第一种方式,这种方式多了一个好处,在使用时可以判断这个错误是否你调用的模块发生的错误。通常,我们调用的模块都会有依赖模块,比如我们用的orm会调用数据库的模块,当数据库的模块发生错误的时候orm通常会直接把错误传递给我们,如果ORMorm块在定义错误的时候采用的是这种方式,那么我们就可以判断得到的错误是不是orm.Err类型,以了解到这个错误到底是在orm模块发生的还是在数据库模块发生的,而且返回错误的过程更加简单。

7.使用Wrap方法定义错误

之前我使用errors包的时候仅仅使用New函数,除此之外,errors包还提供了Unwrap Is As 这三个函数。这就为golang简陋的错误机制稍稍带来了一点点提升。那我们还是尽快用起来吧。

要解释Unwrap函数首先要解释golang提供的Wrap机制,原来我们基于一个错误生成新的错误,通常是使用格式化工具叫原本的错误转成字符串插入到新的错误中。

比如:

oldError := errors.New("旧的错误")
newError := fmt.Errorf("发生了一个错误:%v", oldError)

这样带来的一个问题是原始的错误转换为字符串之后丢失了大量信息,原来的错误没法从新的错误里解析出来,也就没法用比较或者断言的方式判断原来发生的是哪个错误。

能想到的一个最高的方法就是把原来的错误原封不动的保存在新的错误中,需要的时候将原来的错误解析出来。但是golang没有提供Wrap函数,仅仅提供了Unwrap函数,Wrap函数需要我们自己实现。

那么我们可以定义一个错误类型为:

type TheNewError struct {
  oldError error
  errString string
}

func (e TheNewError )Wrap(err error) error {
  e.oldError = err
  return e
}

func (e TheNewError  )Unwrap() error {
  return e.oldError
}

func (e TheNewError  )Error() string {
  return fmt.Sprintf("%v: %v", e.errString,  e.oldError)
}

当需要返回错误的时候执行:

err := execSomeThing()
if err != nil {
  return TheNewError{}.Wrap(err)
}

如果需要获取TheNewError中嵌入的错误,使用errors.Unwrap函数就可以了。errors.Unwrap函数会调用TheNewError的Unwrap函数获取返回值。

使用Wrap机制处理错误会导致原来的使用相等运算和类型判断的方法一定程度上失效,因为返回的错误可以已经被Wrap了。

为此,golang提供了Is和As两个函数来解决这个问题。

Is(err, target error) bool 函数用于判断两个错误是否是同样的错误,Is函数会层次Unwrap 第一个参数并和第二个参数对比,如果存在相等的错误则返回true,否则返回false,这样就可以对Wrap过的错误进行比较。

比如:

var err = errors.New("原始的错误")
wrapedError := TheNewError{}.Wrap(err)
if errors.Is(wrapedError, err) {
 fmt.Printf("%v is %v", wrapedError, err)
}

As(err error, target interface{}) bool 函数用于获取第一个参数中嵌入的类型与target相同的错误,并将该错误解析到target中,如果这个错误不存在,则返回false。所以target必须是指针类型,这样As函数才能进行赋值。

比如:

type TheOldError struct {
  String string
}
wrapedError:= execSomeThing()
target := TheOldError{}
if errors.As(wrapedError, &target) {
  fmt.Printf("%v is wraped from %v", wrapedError, target)
}

虽然golang没有像内建error接口那样内建一个wrapedError,并且我们自己实现Wrap函数似乎也不错。但是还是有更简单方便的用法的。fmt.Errorf 实际上也是支持Wrap机制的。

我们可以使用:

err := execSomeThing()
wrapedError := fmt.Errorf("发生了一个错误: %w", err)

快速创建一个wrapedError,格式化字符串%w就表示一个需要嵌入的错误。

既然golang已经原生支持了wrapedError,我推荐大家还是用起来吧,有总比没有好嘛。随手定义错误虽然开发起来简单,但是会给后续的维护者增添大量负担,并且不利于代码的复用。

扩展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,
  };
}
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!');
  };
  }
}

同样通过 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!');
    };
  }
}

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

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客户机,因为我没有使用背景图片,所以引导菜单看起来是下面的样子,如果你觉得很丑可以使用背景图片。

在原有PXE服务器上配置UEFI启动方式

先前我们已经配置了供BIOS启动使用的PXE服务器了,但是考虑到可能使用超过2T的系统盘,所以在原有的基础上配置供UEFI启动使用的环境。本文同样参考红帽CentOS7的文档准备网络安装章节。

之前我们已经配置过了dhcpd、tftp、httpd服务器了,就不需要重新配置了,只需要在原有的基础上添加UEFI启动文件。

挂载安装镜像光盘到/media目录,拷贝shim和grub2-efi安装包到/root目录,使用rpm2cpio shim-version-architecture.rpm | cpio -dimv命令提取shim包。在/var/lib/tftpboot/创建uefi目录。使用cp publicly_available_directory/boot/efi/EFI/redhat/shim.efi /var/lib/tftpboot/uefi/拷贝shim.efi到uefi目录,使用cp publicly_available_directory/boot/efi/EFI/redhat/grubx64.efi /var/lib/tftpboot/uefi/拷贝grubx64.efi到uefi目录。

在uefi目录创建grub.cfg文件,配置如下:

 

然后拷贝vmlinuz和initrd.img到uefi目录。

现在服务器已经配置好了,可是我还没有能从网络PXE启动的主板,现在还没发测试。

配置 Kickstart 安装

Kickstart的作用就是将安装系统过程中的配置自动化,同时也可以执行简单的脚本。这里我们就用最简单的办法先跑一边 Kickstart 安装。

首先配置一个空白的虚拟机,配置合适的虚拟磁盘,不要配置cdrom,不要安装系统,需要和之前配置的PXE_Server在同一个虚拟网络中。

开机,使用PXE安装,配置成想要的系统。安装完成后可以在/root目录下看到anaconda-ks.cfg文件,这个配置文件就是安装我们刚才的配置生成的。现在我们需要把他放在PXE_Server上,让PXE_Clinet通过网络抓取这个文件。

拷贝anaconda-ks.cfg到PXE服务器的/var/www/html目录下,通过浏览器访问http://192.168.100.3/anaconda-ks.cfg,测试是否能打开kickstart配置文件。然后修改/var/lib/tftpboot/pxelinux/pxelinux.cfg/目录下的default文件。参考红帽教程如何执行Kickstart安装?章节添加inst.ks配置项。如下:

 

使用现在虚拟机在通过PXE安装的时候就可以自动获取到Kickstart配置文件了。

新建一个空白的虚拟机进行测试。安装过程会自动执行,除了输入linux选项,不需要任何输入操作。

使用安装时生成的kickstart 配置文件不能满足我们的需求,我们需要对kickstart进行一定的定制。

1.禁用防火墙和selinux,虽然我非常不建议禁用防火墙和selinux,但是我的同事觉得有selinux和防火墙部署程序经常遇到问题太麻烦了,所以还是先禁用掉防火墙和selinux吧。我们需要在kickstart 配置文件中添加 firewall –disabled 和 selinux –permissive 来实现。

2.安装完成后自动重启,安装程序完成后默认是等待用户进行重启的,但是自动部署不可能等着用户去重启,需要安装完成后就自动重启并完成后面的操作,所以我们需要加入一行 reboot 指令。

3.配置安装后动作。