Code Monkey home page Code Monkey logo

blog's Introduction

👋 Hello! I'm Molin.

HomepageTwitterEmail

I am Dennis Ge, you can call me Molin. I'm a software engineer at Promethean World focusing on IoT and serverless web applications.

  • 🔭 I’m currently working on Golang and Serverless
  • 🌱 I’m currently learning AWS
  • 💬 Ask me about Node.js and Serverless and AWS

Find out more about me & feel free to connect with me.

Technologies

JavaScript Nodejs React C++ HTML5 CSS3 TypeScript MongoDB Redis ElasticSearch GraphQL Apollo GraphQL MySQL Docker Amazon AWS

GitHub Stats

Visitor Badge
Github Stats

blog's People

Contributors

molinjun avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Forkers

shengshaobin

blog's Issues

【排序算法】3.选择排序

选择排序 Selection sort

原理

先看看Wikipedia的定义:

The Selection sort algorithm divides the input list into two parts: the sublist of items already sorted and the sublist of items remaining to be sorted that occupy the rest of the list. Initially, the sorted sublist is empty and the unsorted sublist is the entire input list. The algorithm proceeds by finding the smallest element in the unsorted sublist, exchanging it with the leftmost unsorted element, and moving the sublist boundaries one element to the right. .

所以选择排序的思路就是:

  • 把列表分为两个部分,一部分是已经排好序,一部分待排序。
  • 初始有序子列为空,然后遍历待排序子列,找出最小的元素,然后和待排序子列的第一个元素互换。然后游标右移一个。这样有序子列增加一个元素。
  • 重复以上步骤,直到到最后一个元素,则表示数组有序。

图示

可以通过动画演示理解, 以下网上找的动画。如果你想操作不同的参数来演示,可以上这个网站visualgo.net动手试试。

图示1

代码实现

关于代码,README中代码只有实现算法的函数,具体运行的代码,请查看该目录下的文件。

代码如下:

const selectSort = (array) => {
    // 不修改原数组
    const originValues = array.slice(); 
    const length = originValues.length;
    // 迭代次数 数组长度-1, 因为前n个元素有序,则全部有序
    for (let i = 0; i < length - 1; i++) {
        // 把当前无序子列的第一个元素当做最小值
        let minIndex = i;
        // 找出最小值的索引
        for (let j = i+1; j < length; j++) {
            if (originValues[j] < originValues[minIndex]) {
                minIndex = j;
            }
        }
        // 如果最小值不为当前值,交换
        if (minIndex !== i) {
            const tmp = originValues[i];
            originValues[i] = originValues[minIndex];
            originValues[minIndex] = tmp;
        }
    }

    return originValues;
};

选择排序还是比较简单的,基本知道原理,看了注释就很明白了。有一点要说的是,就是在找最小值这个步骤。很多文章的实现,在发现当前值小于当前最小值时,就交换元素。这种交换还是没必要的,只要先记住最小值得下标minIndex就可以,最后一步来做交换。这样就减少了很多不必要的交换要素,后来发现和wikipedia的实现一模一样(第一次也是唯一一次,哈哈)。

算法分析

时间复杂度
选择排序,不管数组正序还是逆序,都是一样的操作。最优复杂度和最差复杂度都是O(n2)。

稳定性
因为可能存在和最小值元素交换是,把数值相同的元素顺序调换,所以,选择排序是不稳定的排序
举个例子吧:

[3] 5 2 (3) 1

由于最小的元素1在最后一个,需要和[3]元素交换,此时[3]就到(3)后面了。

有文章说选择排序是稳定的,其实看具体的实现。在《算法》第四版217页上作者已经说了,有很多办法可以将任意排序算法变成稳定的,但是,往往需要额外的时间或者空间。摘自知乎

总结

本章节介绍了几种选择排序的实现。选择排序应该是最简单的排序了,不过效率也是很低,复杂度还是O(n2)。

参考

[1] 动画演示
[2] tutorials point 教程
[3] 选择排序究竟属于稳定排序还是不稳定排序?
[4] Sorting Algorithms
[5] 凯耐基梅隆大学数据结构与算法-排序算法
[6] CMU algorithm complexity

发布node模块到npm

原文链接 http://blog.gezhiqiang.com/2017/07/26/npm-module
npm(Node Package Manager)是Node.js的包管理工具。npm社区有很多好用的模块。在开发过程中,我们也会在项目中提炼出一些有用的功能模块。这个时候可以发布到npm并开源到github,方便别人使用同时又进一步根据issue完善和健壮模块的功能。
下面我就根据自己的一个模块koa2-validation来简单介绍发布 npm 模块的方法步骤以及一些注意点。

创建项目

通常我们的项目要放到github上开源,便于别的同学提issue。所以可以创建一个github项目(这里默认大家清楚如何使用github,不明请google)。另外,默认本机已经安装node和npm(不清楚请参考使用nvm安装node)。

项目结构

github创建项目的时候,勾选默认的 .gitignore、License 和 README 文件。本地通过npm init 来创建 package.json 文件,需要数据一些模块的信息,如名称、版本、描述、作者、license等。

$ npm init

另外,需要一些 eslint 文件等,具体的目录结构如下:

.
├── .eslintrc.json   // eslint 文件
├── .git
├── .gitignore      // .gitignore
├── .npmignore      // .npmignore 不需要publish的文件
├── .travis.yml     // travis CI
├── LICENSE
├── README.md
├── index.js       // 入口文件
├── lib            // 主要逻辑代码
├── node_modules
├── package-lock.json
├── package.json
└── test          //测试用例

主要的逻辑代码放在lib目录下。不清楚的同学可以参考koa2-validation的目录结构。

测试

项目通常需要加入必要的单元测试。而且因为可能别的同学会用到,这一点尤为重要。
关于node的测试,大家根据自己的习惯会使用mocha、jest等。我个人偏向于使用ava来进行单元测试。不清楚如何使用ava测试的,可以参考这篇。 关于更多测试结构,参考这里
关于持续集成,在github上通常使用 travis 比较多。travis使用起来也很简单方便。首先需要将你的github 账号和travis关联,travis就会同步你的github repository。然后,你需要打开对应项目的CI 开关。最后在项目中定义对应的.travis.yml即可在项目push的时候自动运行测试脚本。
更多关于travis的使用可以参考这篇官方文档.以下是我的.travis.yml 文件。

#指定运行环境
language: node_js
#指定nodejs版本,可以指定多个
node_js:
  - "7.6.0"

before_script:
  - npm install

#运行的脚本命令
script:
  - npm run test

#指定分支,只有指定的分支提交时才会运行脚本
branches:
  only:
    - master

增加徽章

完成功能开发之后,最好在 README 中详细地介绍模块的使用说明。
另外,我们经常看到 github 项目有很多漂亮的badge, 例如下载量和 测试状况等。那如何添加这些徽章呢。可以参考 shields这篇文章

发布项目

项目完成之后,我们需要将模块发布到npmjs。通常我们需要先查找一下是否自己的模块已经被注册,并确定自己的模块名称。
首先,我们需要到npm官网 注册一个账号。然后添加到本地配置。

$ npm adduser	
Username: dennis.ge
Password: ****
Email: [email protected]

然后验证自己的配置。

npm whoami

最后,在package.json 定义好版本,通过 npm publish 发布模块即可。

npm publish

npm publish的时候经常会遇到发布不成功的状况。

npm WARN adduser Incorrect username or password
npm WARN adduser You can reset your account by visiting:
npm WARN adduser 
npm WARN adduser     http://admin.npmjs.org/reset

这个时候通常可能是nvm安装的npm,或者你的 npm registry 不是npmjs,通过nrm修改npm源即可。

这里只是介绍了一些简单的发布步骤,关于更多版本定义以及升级,请参考这篇

至此,我们就成功发布了一个 npm 模块。

总结

这篇文章简单介绍了一下如何发布 npm 模块。包括如何创建项目,项目结构,测试,持续集成以及如何添加badge等一些内容。
关于发布npm 模块,需要进一步了解版本的定义,以及版本的升级等更深入的内容。

参考

zabbix小结 -- zabbix添加监控主机

按照前面两篇文章zabbix server安装zabbix agent安装,我们已经安装好了zabbix server和zabbix agent。zabbix agent把监控机的数据汇总到zabbix server。zabbix的模板非常丰富,我们可以非常容易的,将一些预定义的模板应用的我们的主机上。
下面我们就介绍一下,如何在界面上添加主机,并如何查看监控的数据。

添加主机到zabbix

登录zabbix

打开http://{ ip | domain }/zabbix 进入zabbix界面,输入用户名、密码登录。

添加主机

点击主菜单的【配置】- 子菜单【主机】- 右边的【创建主机】按钮。如下图。
添加主机

填写主机信息

按要求填写主机的信息:

  • 主机名称
  • 可见名称
  • 代理接口

填写好以上信息,点击【保存】按钮。
填写主机信息

应用模板

点击【模板】子菜单,点击【添加】按钮,出现模板集的窗口,勾选所需模板,点击【选择】。然后在主界面点击【更新】按钮。
添加模板

至此,就添加好主机了。
主机

查看监控图表

zabbix的图表做的很漂亮。你可以选择查看不同的监控指标。

好了,zabbix 算是跑起来了。不过,这才是最基本的应用。后续需要进一步细化各种指标,以及不同的应用场景。

参考

[1] How to Add Host in Zabbix Server to Monitor

Lua CJSON 的安装使用

因为后台接口大体都是使用json的,所以在openrsty的lua脚本中,常需要做json的相关处理。Lua cjson 就是一个用于处理json的lua模块。Openresty 默认是已经安装了cjson的,但是本着学习的态度,还是决定在本地的lua环境下安装和使用。本文将会介绍 lua cjson 基于标准 lua 和 luajit,在linux和mac的安装及简单的使用。
内容没啥难度,只不过在linux和mac,以及lua和luajit间稍微有点差别,自己整的时候刚开始也有点不清楚,所以特别记录一下,防止以后再次踩坑。

lua环境安装

首先,肯定需要安装lua运行时环境:标准lua或者luajit,可以参考lua安装小记 安装。

注意:在笔者写改文章的时候,lua-cjson并不支持lua5.3版本,只支持lua5.1、lua5.2和luaJIT。

这也是我遇到的一个坑,以为是安装出错了。使用的时候会出现以下的错,换成lua5.2就可以了。

dlopen(/usr/local/lib/lua/5.3/cjson.so, 6): Symbol not found: _lua_insert

标准lua

标准lua安装很简单,下载,编译安装即可。

// 下载
curl -R -O http://www.lua.org/ftp/lua-5.3.3.tar.gz
// 解压
tar zxf lua-5.2.4.tar.gz
cd lua-5.2.4

//linux下编译
make linux test
// mac os
make macosx test

//安装
sudo make install

这样lua就安装好了,验证一下。

$ lua -v
Lua 5.2.4  Copyright (C) 1994-2016 Lua.org, PUC-Rio

luajit

luajit的安装也和lua一样。

//下载
git clone http://luajit.org/git/luajit-2.0.git
tar zxf LuaJIT-2.0.4.tar.gz
cd LuaJIT-2.0.4
//编译
make
//安装
sudo make install

验证安装成功。

$ luajit -v
LuaJIT 2.0.4 -- Copyright (C) 2005-2016 Mike Pall. http://luajit.org/

lua cjson 安装

这样lua就安装完成了,接着安装lua cjson。可以直接到官网github上下载源码。

// 通过wget下载
$ wget https://www.kyne.com.au/~mark/software/download/lua-cjson-2.1.0.tar.gz
// 通过git下载
$ git clone [email protected]:mpx/lua-cjson.git

下载解压后,编译需要根据自己的lua环境以及操作系统修改Makefile的一些配置,不然容易出错。
以下是Makefile中的一些配置。

LUA_VERSION =       5.2
TARGET =            cjson.so
PREFIX =            /usr/local
CJSON_LDFLAGS =     -shared
LUA_INCLUDE_DIR =   $(PREFIX)/include
LUA_CMODULE_DIR =   $(PREFIX)/lib/lua/$(LUA_VERSION)
LUA_MODULE_DIR =    $(PREFIX)/share/lua/$(LUA_VERSION)
LUA_BIN_DIR =       $(PREFIX)/bin

最终生成的文件是cjson.so。LUA_VERSION为lua的版本。LUA_INCLUDE_DIR为一些编译用的头文件路径。LUA_CMODULE_DIR是最后cjson.so安装的路径。
在macos环境下,需要修改CJSON_LDFLAGS。Makefile中注释也有介绍,如下:

CJSON_LDFLAGS = -bundle -undefined dynamic_lookup

不然会出现以下错误。

ld: symbol(s) not found for architecture x86_64

对于luajit环境,也要修改相关版本和路径。luajit的lua版本为5.1。我用luaJIT2.0的配置如下:

LUA_VERSION =       5.1
TARGET =            cjson.so
PREFIX =            /usr/local
#CFLAGS =            -g -Wall -pedantic -fno-inline
CFLAGS =            -O3 -Wall -pedantic -DNDEBUG
CJSON_CFLAGS =      -fpic
#CJSON_LDFLAGS =     -shared
CJSON_LDFLAGS =     -bundle -undefined dynamic_lookup
LUA_INCLUDE_DIR =   $(PREFIX)/include/luajit-2.0
LUA_CMODULE_DIR =   $(PREFIX)/lib/lua/$(LUA_VERSION)
LUA_MODULE_DIR =    $(PREFIX)/share/lua/$(LUA_VERSION)
LUA_BIN_DIR =       $(PREFIX)/bin

配置好后,make install安装,就会将生成的cjson.so 复制到LUA_CMODULE_DIR目录下,并修改755权限。

$ sudo make install
mkdir -p //usr/local/lib/lua/5.3
cp cjson.so //usr/local/lib/lua/5.3
chmod 755 //usr/local/lib/lua/5.3/cjson.so

这样lua cjson就可以使用了。

使用

要使用json功能,在lua脚本中引入cjson模块即可。

local cjson = require("cjson");
local cjson_safe = require("cjson.safe")

两者功能差不多,只不过前者在json转码过程有错会立即报错,而后者会返回nil和一条错误信息。

encode

encode用于将lua值或表转化为一个json字符串。

local cjson = require "cjson";

local obj = {
  name = "dennis",
  age = 18
};

local jsonStr = cjson.encode(obj);
print(jsonStr);

-- 输出:{"name":"dennis","age":18}

decode

encode用于将json字符串能转化为一个lua 值或者表。

local cjson = require"cjson";

local jsonStr = '{"name":"dennis","age":18}';

local luaObj = cjson.decode(jsonStr);
print(luaObj.name);
print(luaObj.age);

--输出
dennis
18

这里遇到个有趣的坑,就是关于require的规则。因为我开始写这个lua测试文件的时候,文件命名为cjson.lua。一直出错,我总怀疑是安装错误了。后来修改了文件名,就好了。原来lua脚本中require的时候会现在本地路径查找,再到module库里查找。所以引用本地的cjson的时候就出错了。

总结

本文主要介绍lua cjson的安装和使用。首先介绍了lua和luaJIT在linux和mac的安装。然后介绍了基于lua、luaJIT,在不同操作系统的配置和编译安装。最后介绍了lua cjson的使用。

参考

[1] Lua CJSON 2.1.0 Manual
[2] luajit 安装cjson
[3] lua安装小记
[4] Lua 使用cjson解析json数据(Mac环境)

使用ESLint和Prettier规范代码

JavaScript 是一种动态的弱类型语言。它的语法特性非常灵活,但没有约束同时也带来了很多头疼的问题。不容易捕捉的运行时错误,总是不让人放心。另外,团队的合作开发,成员代码质量的高低以及不同的代码风格,都给代码审查以及项目的质量带来很大的问题。所以,必须有效地制定规则和约定,来规范项目的代码。
ESLint 就是这样一个 JavaScript 的代码检查工具。它的目的就是保证代码的一致性和避免错误。它可以非常方便的集成到编辑器中,同时支持个性化定制自己的规则。 但是,ESLint 只会帮我们提示错误,我们需要另外一个工具帮我们根据规则自动修复一部分错误。 Prettier 就是我们需要的一款代码格式化工具。下面我们将介绍这两款工具的基本使用,以及如何集成到我们的编辑器中。

如果不想关心细节,可以直接查看篇尾的【最佳实践】章节完成配置。

ESLint

ESLint 是一个开源的 JavaScript 代码检查工具,由 Nicholas C. Zakas 于2013年6月创建,使用 Node.js 编写。ESLint 的所有规则都被设计成可插入的,所以可以非常灵活的制定适合自己的检测规则。

安装使用

首先确保系统安装了 Node.js 和 npm。可以在本地安装或者全局安装。

本地安装

如果想让 ESLint 成为构建系统的一部分,建议本地安装。

npm i -D eslint

此时 eslint 指令并不能工作。

$ ./node_modules/.bin/eslint app.js

Oops! Something went wrong! :(

ESLint couldn't find a configuration file. To set up a configuration file for this project, please run:

    eslint --init

ESLint looked for configuration files in /Users/dennis/learn/demo and its ancestors.

If you think you already have a configuration file or if you need more help, please stop by the ESLint chat room: https://gitter.im/eslint/eslint

要使用 eslint --init 来创建一个配置文件。eslint init 需要有 package.json 文件。如果没有,首先使用npm init创建。

./node_modules/.bin/eslint --init

可以根据提示常见自己的配置。

$ ./node_modules/.bin/eslint --init
? How would you like to use ESLint? To check syntax, find problems, and enforce code style
? What type of modules does your project use? JavaScript modules (import/export)
? Which framework does your project use? None of these
? Where does your code run? Node
? How would you like to define a style for your project? Answer questions about your style
? What format do you want your config file to be in? JSON
? What style of indentation do you use? Spaces
? What quotes do you use for strings? Single
? What line endings do you use? Unix
? Do you require semicolons? Yes
? What format do you want your config file to be in? JSON
Successfully created .eslintrc.json file in /Users/dennis/learn/demo

当然大家不要完全依赖提示来创建,你可以根据需求再次修改生成的配置。接下来,我们就可以对我们的文件进行 lint 校验了。

$ ./node_modules/.bin/eslint yourfile.js

如果有 lint 错误,可以 fix 部分错误。

./node_modules/.bin/eslint --fix demo.js

注意:使用本地的 ESLint,使用的插件和配置都需要本地安装。

使用上面的命令还是很不方便,我们可以配置我们的npm 指令。在package.json中加入指令。

 "scripts": {
    "lint": "eslint *.js --fix"
  }

这样就可以通过npm run lint 来 lint 代码了。

npm run lint

全局安装

如果想使 ESLint 使用于所有的项目,建议全局安装。安装和本地安装类似。

$ npm i -g eslint
$ eslint --init
$ eslint yourfile.js

全局安装的 ESLint,使用的任何插件或配置都必须全局安装。

配置

配置方式

ESLint 有两种配置方式:
1. Configuration Comments:在注释中加入配置
这种方式不常用,可以用来在某些场景需要对某个文件或者某一代码行做特殊处理。

文件头尾使用:

/* eslint-disable no-console */

console.log('bar');

/* eslint-enable no-console */

针对特定语句:

// eslint-disable-next-line
alert('foo');

/* eslint-disable-next-line */
alert('foo');

alert('foo'); /* eslint-disable-line */

2. Configuration Files:使用配置文件
使用 JavaScript、JSON 或者 YAML 文件为整个目录和它的子目录指定配置信息。可以配置一个独立的 .eslintrc.* 文件,或者直接在 package.json 文件里的 eslintConfig 字段指定配置,推荐使用。目前 .eslintrc 文件格式将被废弃,所以可以选择 .eslintrc.json

配置项

Environments
指定脚本的运行环境。每种环境都有一组特定的预定义全局变量。常见的如下:

browser - 浏览器环境中的全局变量。
node - Node.js 全局变量和 Node.js 作用域。
es6 - 启用除了 modules 以外的所有 ECMAScript 6 特性(该选项会自动设置 ecmaVersion 解析器选项为 6)。
jest - Jest 全局变量。

Globals
脚本在执行期间访问的额外的全局变量。如果你想在一个源文件里使用全局变量,推荐你在 ESLint 中定义这些全局变量,这样 ESLint 就不会发出警告了。
要在你的 JavaScript 文件中,用注释指定全局变量,格式如下:

/* global var1:false, var2:false */

定义全局变量 var1var2。var1 允许被重写,var2 不允许被重写。
在配置文件里配置全局变量时

{
    "globals": {
        "var1": true,
        "var2": false
    }
}

Parser Options
指定 JavaScript 的语言选项。默认情况下,ESLint 支持 ECMAScript 5 语法。

{
    "parserOptions": {
        "ecmaVersion": 6,
        "sourceType": "module",
        "ecmaFeatures": {
            "jsx": true
        }
    },
    "rules": {}
}

Parser
ESLint 默认使用Espree作为其解析器,你可以在配置文件中指定一个不同的解析器。

Rules 启用的规则及其各自的错误级别
Rules 用来定义一些规则。Eslint有很多默认的规则,这个不用全部去记,只需要使用的过程中逐个去了解就可以了。可以参考官网规则
每个规则可以定义三种错误级别:

  • "off" 或 0 - 关闭规则
  • "warn" 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出)
  • "error" 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)

可以直接定义规则的等级,有些规则支持一些额外选项。如下:

//定义等级
'class-methods-use-this': 0,
// 支持选项
'no-unused-expressions': [2, {
    allowShortCircuit: true,
    allowTernary: true
}]

第一个参数为错误等级。第二个参数为可选项。

extends
用来继承一些通用的配置,如官网的eslint-recommended。也可以使用 AirBnB 规范。AirBnB 会相对比较严格一些,可以根据自己的需求选择。AirBnB 可以根据自己是否使用 react 和 jsx 来选择使用通用版或者 airbnb-base 版。
使用通用版需要安装一下依赖。

npm i -D eslint-plugin-react
npm i -D eslint-plugin-jsx-a11y

Plugin 第三方插件
插件是一个 npm包,通常输出规则。一些插件也可以输出一个或多个命名的配置。使用插件之前,需要 npm 安装。配置文件中的插件名称可以省略 eslint-plugin- 前缀。

{
    "plugins": [
        "plugin1",
        "eslint-plugin-plugin2"
    ]
}

extends 属性值可以由以下组成:

  • plugin:
  • 包名 (省略了前缀,比如,react)
  • /
  • 配置名称 (比如 recommended)
{
    "plugins": [
        "react"
    ],
    "extends": [
        "eslint:recommended",
        "plugin:react/recommended"
    ],
    "rules": {
       "no-set-state": "off"
    }
}

集成 VSCode

ESLint 可以非常容易地集成到各种编辑器。这里介绍一下集成到 VSCode,别的编辑器请参看官网
VSCode 提供了 ESLint 的插件,在 VSCode 安装即可。安装完成后,VSCode 会查找最近安装的 ESLint 和配置文件来 lint 代码,并显示错误的格式。

Prettier

Prettier 是一款代码格式化工具。Prettier 和 ESLint 是两种不同功能的工具,不要混淆,可以参考这里

安装使用

具体请参考安装说明

npm install --save-dev --save-exact prettier
# or globally
npm install --global prettier

如果我们在 VSCode 中使用,需要在 VSCode 安装 prettier-vscode 插件。安装完成后,可以使用指令格式化代码。

1. CMD + Shift + P -> Format Document
OR
1. Select the text you want to Prettify
2. CMD + Shift + P -> Format Selection

当然,也可以修改 editor.formatOnSave 配置,在文件保存时,运行 format。

// Set the default
"editor.formatOnSave": false,
// Enable per-language
"[javascript]": {
    "editor.formatOnSave": true
}

配置

Prettier 的配置通常放在 .prettierrc文件中,当然也支持 json、toml等文件格式。更多的配置说明请看这里
常见的一些配置如下:

{
  "printWidth": 80,        // 每行字符数
  "trailingComma": "es5",  // 是否允许尾部的逗号
  "tabWidth": 4,           // 每个缩进的空格数
  "semi": true,            // 是否需要分号
  "singleQuote": true      // 单引号或双引号
}

ESLint 与 Prettier 集成

Prettier 可以集成到 ESLint 中,这样就可以使用 Prettier 来负责格式化相关问题,同时 ESLint 来负责检测代码的错误语法问题。
将 Prettier 集成到 ESLint 主要就两个步骤:

1. 禁用 ESLint 中格式化相关的规则

ESLint 中有些格式化规则可能与定义的 Prettier 规则有冲突。为了确保格式化规则全由 Prettier 决定,我们需要禁用 ESLint 中的相关规则。
eslint-config-prettier 可以用来完成这个功能。首先安装这个依赖:

npm i -D eslint-config-prettier

然后在 .eslintrc.jsonextends 数组的最尾添加 prettier

{
  "extends": [
    "some-other-config-you-use",
    "prettier"
  ]
}

2. 使用 ESLint 来运行 Prettier

使用 eslint-plugin-prettier 来将 Prettier 的格式化内容,作为一个规则(rule)加到 ESLint。

npm i -D eslint-plugin-prettier

修改 .eslintrc.json

{
  "plugins": ["prettier"],
  "rules": {
    "prettier/prettier": "error"
  }
}

推荐配置

eslint-plugin-prettier 提供一个 recommended 配置项,来一步完成上述配置。

npm i -D eslint-config-prettier eslint-plugin-prettier

然后在 .eslintrc.json 中修改。

{
  "extends": ["plugin:prettier/recommended"]
}

最佳实践

本方法适用于 VSCode 的场景。大家可以设计适合自己的场景。

步骤

启用ESLint

  • 本地目录安装 ESLint。不要全局安装,减少复杂度。
npm i -D eslint
  • VSCode 安装 ESLint 插件
  • 添加 .eslintrc.json配置文件
    如果没有 package.jsonnpm init 生成。
./node_modules/.bin/eslint --init

可以根据提示生成适合自己的配置。最好根据不同的项目设置不同的配置,例如前端 React 项目可能要启用 React 插件等。

启用 Prettier

  • 本地安装 Prettier
npm install --save-dev --save-exact prettier
  • VSCode 安装 Prettier 插件。
  • 创建 .prettierrc 文件添加配置。
  • Prettier 集成到 ESLint。
    安装 eslint-config-prettier 覆盖 ESLint 的格式化配置。 安装 eslint-plugin-prettier 并把 Prettier 设置成 ESLint 的 rule。
npm i -D eslint-config-prettier eslint-plugin-prettier

修改 .eslintrc.json 配置。

{
  "extends": ["plugin:prettier/recommended"]
}
  • 设置 editor.formatOnSave配置,在文件保存时,运行 format。

参考配置
.eslintrc.json 文件如下:

{
  "env": {
    "es6": true,
    "node": true
  },
  "extends": ["eslint:recommended", "prettier"],
  "globals": {
    "Atomics": "readonly",
    "SharedArrayBuffer": "readonly"
  },
  "parserOptions": {
    "ecmaVersion": 2018,
    "sourceType": "module"
  },
  "rules": {
    "linebreak-style": ["error", "unix"],
    "quotes": ["error", "single"],
    "semi": ["error", "always"],
    "prettier/prettier": "error"
  }
}

.prettierrc 文件如下:

{
  "trailingComma": "all",
  "tabWidth": 2,
  "semi": true,
  "printWidth": 80,
  "singleQuote": true
}

参考

[1] ESLint官网
[2] Prettier官网
[3] Integrating Prettier + ESLint + Airbnb Style Guide in VSCode
[4] Write cleaner code using Prettier and ESLint in VSCode
[5] 深入浅出eslint——关于我学习eslint的心得

【排序算法】1.算法特性及大O记法

算法特性及大O记法

排序算法

排序算法(Sorting algorithms)是什么? Wikipedia 如是说:

In computer science, a sorting algorithm is an algorithm that puts elements of a list in a certain order.

也就是说,排序算法,就是某种算法,将列表中的元素按照某种规则排序。常见的如数字大小排序、字典序排序等。本系列例子约定为从小到大的数字排序,其他的类似,关键在于思路。

算法特性

1、内部排序和外部排序

按照数组规模的大小,排序可以分为内部排序外部排序
内部排序(internal sorting): 全部数组都可放在内存中排序。
外部排序(external sorting): 数组太大,不能全部放在内存中,部分数据在硬盘中。

本系列约定为内部排序,关于海量数据的排序,后续补充。

2、稳定性
排序法的稳定性(stability): 取决于值相等的两个元素,排序之后是否保持原来的顺序。

3、比较排序和非比较排序
比较排序(comparison sort):
比较排序中,每一步通过比较元素大小来决定元素的位置。其复杂度由比较次数交换次数来决定。比较排序比较好实现,但是时间复杂度无法突破 O(nlogn)。证明过程,可以参考这篇文章

非比较排序(non-comparison sort):
非比较排序,如桶排序,不通过比较,后续将会讲解。这类算法可以突破 O(nlogn)

排序算法有很多种,每一种都各自有自己的优点缺点和不同的应用场景,没有一种排序是绝对完美的。如何评价一个算法的优劣呢,我们通过算法复杂度来衡量。

算法复杂度

算法复杂度(complexity),可以从时间复杂度空间复杂度两个维度来考虑。
空间复杂度,是指算法所需要的额外的存储单元。目前的硬件条件,这一块通常可以不考虑了。算法优化,更多是来优化算法的时间。

下面将介绍如何来估算时间复杂度。下面的介绍的方法,目前只够勉强说服我自己。如果觉得不想了解这个理论,可以直接记住下面的结论。如果觉得讲得不是那么容易懂,可以参考别的资料仔细研究。

时间复杂度

如果一个列表的大小为n,则算法耗费的时间T(n)。但是由于机器、CPU等的不同,同一个算法执行的时间可能都不一样。所以通常不是按耗费的时间来计算,而是用某个算法实现的指令执行的次数,来衡量时间复杂度。如下面这个程序:

for( i = 0; i < n; i++)   // i = 0; 执行1次
       			  // i < n; 执行n+1次
			  // i++    执行n次
  sum = sum + i;          //    执行n次
  
// 总次数f(n) = 1 + n+1 + n +n = 3n+2

通过上面计数操作数的方法,显得很麻烦。所以通常是通过一个函数来估算,确保它是算法操作数f(n)的上界。这种方法就是大O记法

大O记法

对于单调函数 f(n) 和 g(n), n为正整数,如果存在常数c > 0, n0 > 0,且

$$f(n) ≤ c * g(n), n ≥ n0$$

则我们称

$$f(n) = O(g(n))$$

如下图所示。
大O记法

简单来说,就是当n→∞时,f(n)的增长率不大于g(n),也就是说g(n)时f(n)的上界。
在这里,f(n)就是算法的指令操作数,而g(n)就是我们估算的复杂度上界。
它还有两个特性。

O(g1(N)) + O(g2(N)) = max(O(g1(N)), O(g2(N)))
O(g1(N)) * O(g2(N)) = O(g1(N) * g2(N))

所以,上面程序的时间复杂度是:

f(n) = 3n+1 = O(1) + O(n) + O(n) + O(n) = O(n)
  • 常数时间 O(1)
    常数时间(constant time),算法的执行时间和列表大小无关。
  • 线性时间 O(n)
    线性时间(linear time), 算法执行时间和列表大小成正比。
  • 对数时间 O(logn)
    对数时间(logarithmic time), 稍微显得难理解一点。不过如果你了解对数,其实也很简单。例如二分查找,每一次查找都会去掉一半的元素,但最后一次元素个数就是1。假设数组大小为n, 要经过x轮查找,则
$$n * (1/2)^x = 1$$

$$x = log_{2}n$$

logn是简写,一般忽略底数。

  • 二次项时间 O(n2)
    二次项时间(quadratic time), 通常是两层循环的算法。

简易估算方法

对于一个算法的时间复杂度,根据以上理论,大体按下面的步骤来估算复杂度。
以这个程序为例:

sum = 0;            
for( i = 0; i < n; i++)
    for( j = i; j < n; j++)
        sum++;

1. 忽略简单语句
对于简单复杂语句,它执行次数是一个常数,复杂度为O(1)。如果还存在循环,O(1)对结果不影响。

2. 关注循环语句
对于循环语句,要认真分析其循环执行的次数。例子中,外层循环要执行 n 次,内层循环要

    n + (n-1) + ... + 2 + 1 = (n + 1)/2

所以总次数T(n)为

$$T(n) = n * (n+1)/2 = 1/2*n^2 + 1/2*n$$

3. 忽略常数项,保留高次项
对于一个多项式,当n→∞时,完全由最高项次决定。所以

$$T(n) = O(1/2*n^2 + 1/2*n) = O(n^2 + n) = O(n^2)$$

对于有的程序,复杂度还是很不好计算。所以要多练习,写一个程序之后,自己主动去算一下它的复杂度,慢慢就熟练了。

算法评价

对于排序算法,一个算法的执行性能,和输入的数据有很大的关系。对于某些特定的数据,某些算法的效率很高,但通常算法的性能又很低。所以通常存在:

  • 最优时间复杂度:某些数据,执行的次数最少
  • 最差时间复杂度:某些数据,执行的次数最多
  • 平均时间复杂度:平均需要执行的次数

通常还是以平均时间复杂度,来衡量算法。例如冒泡排序,当数组元素有序时,最优时间复杂度为O(n)。当逆序是,为O(n2)。平均还是O(n2)。算法复杂度的优劣,可以参考此图:
算法比较

总结

本章节主要介绍了一下排序算法的类型,以及如果通过大O记法来评价一个算法。对于如何计算算法的时间复杂度,很多人都感觉很头疼。我给的建议是,按照上面的步骤多练习,多去主动算程序的时间复杂度。这样慢慢自己就会掌握技巧,并且提醒自己保证自己程序的执行效率。共勉!

资源与参考

[1] About the #sorting-algorithms series
[2] 凯耐基梅隆大学数据结构与算法-排序算法
[3] CMU algorithm complexity
[4] Big O cheat sheet
[5] You need to understand Big O notation, now

zabbix小结 -- zabbix agent安装

根据上篇文章zabbix server 安装小结,已经成功安装好了zabbix server。接下来,我们需要在需要监控的主机上安装zabbix agent。zabbix agent用来收集主机的资源利用率和应用数据,并上报给zabbix server做统计。
下面就详细介绍下zabbix agent的安装过程。

添加apt repository

For Ubuntu 16.04 LTS:
$ wget http://repo.zabbix.com/zabbix/3.0/ubuntu/pool/main/z/zabbix-release/zabbix-release_3.0-1+xenial_all.deb
$ sudo dpkg -i zabbix-release_3.0-1+xenial_all.deb
$ sudo apt update

For Ubuntu 14.04 LTS:

$ wget http://repo.zabbix.com/zabbix/3.0/ubuntu/pool/main/z/zabbix-release/zabbix-release_3.0-1+trusty_all.deb
$ sudo dpkg -i zabbix-release_3.0-1+trusty_all.deb
$ sudo apt-get update

安装zabbix agent

添加好apt repository之后,通过下面命令安装agent。

$ sudo apt-get install zabbix-agent

修改zabbix agent配置

zabbix agent 的配置文件位于/etc/zabbix/zabbix_agentd.conf。修改一下配置:

#Server=[zabbix server ip]
#Hostname=[Hostname of client system ]

Server=192.168.1.11
Hostname=Server2

启停zabbix agent

配置好了之后,可以通过下面命令启动和停止zabbix agent。

# /etc/init.d/zabbix-agent restart
# /etc/init.d/zabbix-agent start
# /etc/init.d/zabbix-agent stop

至此,zabbix agent安装完毕。zabbix就会收集监机的数据,并上报给zabbix server。

参考

[1] How To Install Zabbix Agent on Ubuntu

【排序算法】2.冒泡排序

冒泡排序 Bubble sort

原理

先看看Wikipedia的定义:

Bubble sort is a simple sorting algorithm that repeatedly steps through the list to be sorted, compares each pair of adjacent items and swaps them if they are in the wrong order. The pass through the list is repeated until no swaps are needed, which indicates that the list is sorted.

所以冒泡排序就是,每次比较相邻的两个元素,如果顺序不对,则交换元素。每一次迭代,最大的元素会排到列表的一侧。然后重复这个步骤,直到不需要交换元素,该数组即有序了。

图示

可以通过动画演示理解, 以下网上找的两个动画。如果你想操作不同的参数来演示,可以上这个网站visualgo.net动手试试。

图示1

图示2

代码实现

关于代码,README中代码只有实现算法的函数,具体运行的代码,请查看该目录下的文件。

初始冒泡

代码如下, 看bubble_sort_1.js

const bubbleSort = (array) => {
    // 不修改原数组
    const originValues = array.slice(); 

    // 迭代次数 数组长度-1
    for (let i = 0; i < originValues.length - 1; i++) {
        // 两两比较,该迭代的最大数,移动到右侧相应位置
        for (let j = 0; j < originValues.length - 1 - i; j++) {
            // 如果前一个数,大于后一个数,交换
            if (originValues[j] > originValues[j + 1]) {
                const tmp = originValues[j];
                originValues[j] = originValues[j + 1];
                originValues[j + 1] = tmp;
            }
        }
    }

    return originValues;
};

代码其实已经很明显了。最外层循环控制迭代次数,内层循环两两比较,把较大的数往右移动。不过有几点要提一下:

  • 最外层循环为 (length-1)
    这个看很多实现都是外层循环length次,其实最后一次多余了,因为只要前 n-1 都排序之后,第一个数肯定是最小的数了。
  • 内层循环可以忽略已经排好序的元素
    每过n轮,则最右侧的n个元素肯定有序了。交换排序的时候,可以忽略这些元素。所以
    内层循环的终止游标是length-1-i

复杂度分析:
很明显,不管数组正序还是逆序,复杂度都是O(n2),所以最优复杂度和最坏复杂度都是O(n2)。 可资料上都是说,冒泡排序的最优复杂度是O(n)啊,那上面这种实现,肯定可以优化。
仔细复盘上面的流程发现: 如果数组正序,比较一轮数组之后,后面还会傻傻地重复进行比较。而这其实是没有必要的。只要在某一轮比较中,没有发生元素互换,就可以确认数组已经有序了

改进的冒泡

代码如下, 看bubble_sort_2.js

const bubbleSort = (array) => {
    // 不修改原数组
    const originValues = array.slice(); 

    let swapped = true;
    do {
        // 标记该次迭代是否交换顺序,如果没有,代表列表已经有序
        swapped = false;
        for (let i = 0; i < originValues.length - 1; i++) {
            // 如果前一元素大于后一元素,交换顺序
            if (originValues[i] > originValues[i + 1]) {
                const tmp = originValues[i];
                originValues[i] = originValues[i + 1];
                originValues[i + 1] = tmp;
                // 如果有交换,标记继续下一轮
                swapped = true;
            }
        }
    } while (swapped);

    return originValues;
};

复杂度分析:
经过上面的改造:当数组正序时,也就是最优复杂度达到了O(n);当数组逆序时,为最坏复杂度,为O(n2)。
咋一看,貌似是最终解了。但复盘之后,发现每轮已经排序的元素,还会重复去比较。所以还可以小优化一下。

冒泡再修改

代码如下, 看bubble_sort_3.js

const bubbleSort = (array) => {
    // 不修改原数组
    const originValues = array.slice(); 

    let swapped = true;
    let hasOrderedCnt = 0; // 已排序个数
    do {
        // 标记该次迭代是否交换顺序,如果没有,代表列表已经有序
        swapped = false;
        for (let i = 0; i < originValues.length - 1 - hasOrderedCnt; i++) {
            // 如果前一元素大于后一元素,交换顺序
            if (originValues[i] > originValues[i + 1]) {
                const tmp = originValues[i];
                originValues[i] = originValues[i + 1];
                originValues[i + 1] = tmp;
                swapped = true;
            }
        }
        // 每一轮之后,都有一个元素排好顺序
        hasOrderedCnt++;
    } while (swapped);

    return originValues;
};

用了一个变量hasOrderedCnt来记录已经排序的个数,这样内循环就不要去比较已经排序的元素了。

算法分析

  • 时间复杂度
    经过几轮修改,数组正序时,最优复杂度可以达到O(n);逆序时,最差复杂度O(n2)。

  • 稳定性
    算法中,每次只有前一个元素大于后一个元素,才会进行交换。所以数值相同的两个元素,不会发生位置互换,所以可以保持之前前后顺序。故,冒泡排序是稳定的排序

总结

本章节介绍了几种冒泡排序的实现。算法**还是算简单的,但也是效率比较慢的。虽然比较简单,但还是有很多变种,例如左右冒泡、从大到小的排序、数组元素不是数值等等,都需要自己动手去写才能理解透。

参考

[1] 动画演示
[2] tutorials point 教程
[3] The Bubble sort algorithm
[4] Sorting Algorithms

zabbix小结 -- zabbix server安装

zabbix是一个基于WEB界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案。
zabbix由2部分构成,zabbix server与可选组件zabbix agent。
zabbix server可以通过SNMP,zabbix agent,ping,端口监视等方法提供对远程服务器/网络状态的监视,数据收集等功能。zabbix agent需要安装在被监视的目标服务器上,它主要完成对硬件信息或与操作系统有关的内存,CPU等信息的收集。
下面将详细介绍zabbix server的安装。

安装LAMP环境

安装apache2

$ sudo apt-get update
$ sudo apt-get install apache2

安装MySQL

$ sudo apt-get install mysql-server

安装的过程成需要输入root的密码,然后再次输入密码确认。

安装php

ubuntu 14.04

$ sudo apt-get install php5 php5-cli php5-common php5-mysql

ubuntu 16.04

$ sudo apt-get install php7.0 php7.0-cli php7.0-common php7.0-mysql

ubuntu16.04里apt-get安装的默认就是7.0,已经不是5.0。这也是自己遇到的一个坑。下面的安装中,两个版本还是有细微差异的。

修改时区

ubuntu 14.04
修改/etc/php5/apache2/php.ini文件中的timezone

date.timezone = 'Shanghai'

ubuntu 16.04
这时并没有找到php目录下有apache2。需要再安装libapache2-mod-php7.0

$ sudo apt-get install libapache2-mod-php7.0

安装完成后,修改/etc/php/7.0/apache2/php.ini文件中的timezone

date.timezone = 'Asia/Shanghai'

增加zabbix server的apt repository

在安装zabbix server之前,先配置好它的rpm repository。

For Ubuntu 16.04 LTS:

$ wget http://repo.zabbix.com/zabbix/3.0/ubuntu/pool/main/z/zabbix-release/zabbix-release_3.0-1+xenial_all.deb
$ sudo dpkg -i zabbix-release_3.0-1+xenial_all.deb
$ sudo apt-get update

For Ubuntu 14.04 LTS:

$ wget http://repo.zabbix.com/zabbix/3.0/ubuntu/pool/main/z/zabbix-release/zabbix-release_3.0-1+trusty_all.deb
$ sudo dpkg -i zabbix-release_3.0-1+trusty_all.deb
$ sudo apt-get update

安装zabbix server

设置好apt repository 之后,就安装使用mysql的zabbix server。

$ sudo apt-get install zabbix-server-mysql zabbix-frontend-php

创建数据库schema

现在需要为zabbix server创建数据库的schema。

创建数据库和用户

$ mysql -u root -p
//创建数据库zabbixdb
mysql> CREATE DATABASE zabbixdb;
//授予zabbixdb给本地的zabbix用户,并设置密码
mysql> GRANT ALL on zabbixdb.* to zabbix@localhost IDENTIFIED BY 'password';
mysql> FLUSH PRIVILEGES;

基于新建的数据库,重启zabbix database schema

$ cd /usr/share/doc/zabbix-server-mysql
$ zcat create.sql.gz | mysql -u root -p zabbixdb

这里需要输入密码,是root的密码,也就是创建mysql数据库时的密码,而不是zabbix数据库的密码。

编辑zabbix的配置文件

zabbix的配置文件位于/etc/zabbix/zabbix_server.conf。修改以下内容。

DBHost=localhost
DBName=zabbixdb
DBUser=zabbix
DBPassword=password

重启Apache 和 Zabbix

zabbix会创建自己的apache 配置文件,位于/etc/zabbix/apache2.conf。使用如下命令重启apache服务。

$ sudo service apache2 restart

zabbix的配置文件位于/etc/zabbix/zabbix_server.conf。使用如下命令重启:

$ sudo service zabbix-server restart

启动好zabbix server之后,就可以开始zabbix 的web安装。

启动zabbix web 安装。

启动zabbix server之后,通过浏览器打开如下url,根据提示安装。

http://<域名|ip>/zabbix

zabbix安装的欢迎界面

点击下一步按钮。
检查需要安装的依赖
检查系统是否有所有安装的包,如果有没安装的,使用apt-get 安装。

ubuntu16.04 会出现没有xml-reader、xmlwriter等包的情况。


确认好之后,点击下一步。
配置数据库连接
根据上面建立的数据库,设置数据库的连接,数据库名字,用户及密码。

设置好之后,点击下一步。
zabbix server详情
数据设置成功之后,会显示zabbix server的详情。

显示上面设置的概况

安装zabbix

登录zabbix
使用默认账户登录。

Username:  admin
Password:  zabbix


至此,zabbix-server就安装成功了。接下来安装zabbix agent 和在zabbix server中增加主机。

参考

[1]Ubuntu下安装Zabbix
[2]How to Install Zabbix Server 3.0 on Ubuntu

【排序算法】4.插入排序

插入排序 Insertion sort

原理

先看看Wikipedia的定义:

Insertion sort algorithm iterates, consuming one input element each repetition, and growing a sorted output list. Each iteration removes one element from the input data, finds the location it belongs within the sorted list, and inserts it there. It repeats until no input elements remain.

所以插入排序的思路就是:

  • 把列表分为两个部分,一部分是已经排好序,一部分待排序。(这一点和选择排序类似)
  • 初始将第一个元素作为有序子列为,然后每次迭代从有序序列中移除;然后将它插入到有序序列中的相应位置。
  • 重复以上步骤,直到到最后一个元素,则表示数组有序。

图示

可以通过动画演示理解, 以下网上找的两个动画。如果你想操作不同的参数来演示,可以上这个网站visualgo.net动手试试。

图示1

图示2

代码实现

关于代码,README中代码只有实现算法的函数,具体运行的代码,请查看该目录下的文件。

代码如下:

const insertSort = (array) => {
    // 不修改原数组
    const originValues = array.slice(); 

    // 初始将第一个元素指定为有序子列,从第2个元素开始插入,直到n-1元素 
    for (let i = 1; i < originValues.length; i++) {
        const currentValue = originValues[i];
        // 标记插入有序子列的位置
        let insertIndex = i;
        // 将当前元素从右到左与有序子列元素比较
        // 起始位置为当前元素前一个元素,即i-1,终止位置为0
        // 如果当前元素比该有序子列元素小,则该元素后移一位,并修改插入位置的游标
        for (let j = i-1; j > -1 && currentValue < originValues[j]; j--) {
           originValues[j+1] = originValues[j];
           insertIndex = j;
        }
        // 插入指定位置
        originValues[insertIndex] = currentValue;
    }

    return originValues;
};

以上就是直接插入排序的代码实现。总体来说还是比较简单易懂,其实就类似于打扑克,不断将扑克牌按顺序插入指定位置。唯一可能有一点容易想不清楚的,就是有序子列的右移部分。想清楚一点,只要有序子列的该元素大于要插入的元素,该元素就要后移一位。

算法分析

1. 时间复杂度
排序算法中,两个元素的比较次数是影响运行时间的首要因素。所以我们可以通过这个层面来评估。
最优复杂度
当数组有序时,每一次迭代只有一次比较,所以总共有n-1次比较,复杂度为O(n)。
最差复杂度
当数组逆序时,每一次都要比较整个有序子列,比较次数为:

T(n) = 1 + 2 + ... + n-1 = n*n/2

所以,最差复杂度是O(n2)。

2. 稳定性
因为比较元素是,相同值不会交换,所以插入算法是稳定的。

总结

本章节介绍了插入排序的实现。插入排序在思路上,和选择排序还是有些类似的。插入排序的复杂度还是没有突破O(n2)。
不过,上面的实现只是简单的插入排序,还可以优化。就是在有序子列中找插入位置时,利用二叉查找的方法,这样复杂度可以到O(nlogn)。这里先不做介绍。

参考

[1] 动画演示
[2] tutorials point 教程
[3] Sorting Algorithms
[4] 凯耐基梅隆大学数据结构与算法-排序算法

利用Minikube来部署一个nodejs应用

Kubernetes 是 docker 容器编排系统, 用于协调高可用的计算机集群,并在这个集群上以更有效的方式自动分发和调度应用程序。由于kubernetes本地安装比较复杂,所以社区推出了Minikube。Minikube 是在本地的虚拟机中运行一个单节点的kubernetes集群。
本文将会介绍Minikube的安装,在kubernetes集群上部署一个应用,并且简单扩容以及通过rolling update更新镜像。最后会简单介绍一下 kubernetes dashboard的简单使用。

搭建kubernetes环境

安装Minikube

Minikube 可以安装在linux、mac和windows上。我们只介绍在mac 以及 ubuntu 14.04上的安装。更多关于安装Minikube请参考官方教程
Minikube是使用go开发生成的二进制文件,下载并放到/usr/local/bin即可。

// linux 下安装
curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/ 
// mac
brew cask install minikube //可能不是最新
// 也可以下面的方式
curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.21.0/minikube-darwin-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/

验证安装成功。

$ minikube version
minikube version: v0.21.0

安装vm

因为Minikube是在host上启动一个VM来运行Kubernetes集群的,所以还要安装一个VM。
下面介绍mac下安装xhyve和 linux下安装virtual box。更多关于vm的安装,请参考下面的安装指导

// linux virtualbox
sudo apt-get install virtualbox

// macos xhyxhyve
brew install docker-machine-driver-xhyve
sudo chown root:wheel $(brew --prefix)/opt/docker-machine-driver-xhyve/bin/docker-machine-driver-xhyve
sudo chmod u+s $(brew --prefix)/opt/docker-machine-driver-xhyve/bin/docker-machine-driver-xhyve

安装kubectl

Kubectl是kubernetes的命令行工具,用来 管理部署在kubernetes上的应用。可以根据自己的操作系统,按照官网教程安装kubectl。

// 1. 下载
// linux 
$ curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
// mac
$ curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/darwin/amd64/kubectl
或者
$ brew install kubectl

// 2. 更改执行权限 并移到指定位置。
$ chmod +x ./kubectl
$ sudo mv ./kubectl /usr/local/bin/kubectl

验证kubectl是否安装成功。

$ kubectl version
Client Version: version.Info{Major:"1", Minor:"7", GitVersion:"v1.7.0", GitCommit:"d3ada0119e776222f11ec7945e6d860061339aad", GitTreeState:"clean", BuildDate:"2017-06-29T23:15:59Z", GoVersion:"go1.8.3", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"7", GitVersion:"v1.7.0", GitCommit:"d3ada0119e776222f11ec7945e6d860061339aad", GitTreeState:"clean", BuildDate:"2017-07-26T00:12:31Z", GoVersion:"go1.8.3", Compiler:"gc", Platform:"linux/amd64"}

这里有可能会出现下面的错误状况。

Client Version: version.Info{Major:"1", Minor:"7", GitVersion:"v1.7.2", GitCommit:"922a86cfcd65915a9b2f69f3f193b8907d741d9c", GitTreeState:"clean", BuildDate:"2017-07-21T19:06:19Z", GoVersion:"go1.8.3", Compiler:"gc", Platform:"darwin/amd64"}
The connection to the server localhost:8080 was refused - did you specify the right host or port?

这里只有客户端的信息,没有服务端的信息,那是因为我们的Minikube还没有启动,下面我们就启动Minikube。

启动Minikube

安装上面的工具之后,就可以通过minikube start来启动。

$ minikube start
Starting local Kubernetes v1.7.0 cluster...
Starting VM...
Getting VM IP address...
Moving files into cluster...
Setting up certs...
Starting cluster components...
Connecting to cluster...
Setting up kubeconfig...
Kubectl is now configured to use the cluster.

也可以通过--vm-driver指定虚拟机启动。

minikube start --vm-driver xhyve

这样minikube就启动了。可以通过kubectl cluster-info查看集群的信息。

$ kubectl cluster-info
Kubernetes master is running at https://192.168.99.100:8443

可以看到一个运行的master。
也可以通过kubectl get nodes查看运行的node(目前只有一个)。

$kubectl get nodes
NAME       STATUS    AGE       VERSION
minikube   Ready     13m       v1.7.0

这里还要介绍个概念就是context。context决定kubectl与哪个cluster交互。可以在~/.kube/config中查看。通过以下指令指定context。

kubectl config use-context minikube

默认就是minikube。

部署应用

启动kubernetes cluster之后,就可以部署应用到集群上。部署一个应用需要创建一个Deployment。Deployment 负责创建和更新应用程序实例。创建Deployment之后,kubernetes master 会将创建的应用程序调度到各个node上。
创建应用实例后,kubernetes deploymentController 会监视这些实例,如果某个节点不可用或者删除,会自动替换实例。
创建部署时,通常需要指定应用程序的镜像副本数。以下部署一个简单的node应用。

const express = require('express');
const http = require('http');

const app = express();

app.get('/', (req, res) => {
  return res.send({ success: true, msg: 'hello kubernetes'});
});

http.createServer(app).listen(1226, () => {
  console.log('Server is listening on port 1226');
});

这个镜像我push到了docker hub

这里因为刚开始试的时候,Minikube里一直拉取不了镜像。正确的方式应该是搭建一个private docker registry。但折腾中还有点不明白之处,所以这里先不做具体介绍,直接push到docker hub。后续会介绍更多关于docker registry的搭建,到时候顺便再一起解释。

也可以使用minikube VM的docker来拉镜像。

$ eval $(minikube docker-env)

如果不需要使用minikube的docker,可以再恢复。

$ eval $(minikube docker-env -u)

使用kubectl run 创建一个部署。

$ kubectl run docker-demo --image=docker_demo --port=1226

image指定镜像,port指定端口。这里要注意的是,启动的容器名docker-demo,只能是小写字母、数字和加 - 横线。
这样我们就部署了一个应用到node上。通过kubectl get deployments查看。

$ kubectl get deployments
NAME          DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
docker-demo   1         1         1            0           22s

如果pods一直处于ContainerCreating阶段,可能还是还不能拉取镜像。

可以通过minikube ssh进入vm,然后拉取对应镜像。

$ minikube ssh
$ docker pull dennisge/docker_demo

这时,可以看到我们的pod的状态是running。到此,我们就成功部署了我们的应用。

基本概念和指令

Kubernetes 集群由两种类型的资源组成:masterNodes。 master是集群的调度节点,nodes则是应用程序实际运行的工作节点。类似于nginx的 master 和 worker。
部署应用时需要创建deployment。每个deployment 会根据指定的副本数,创建相应的pod来托管我们的应用实例。Pod可以理解为应用实例特定的逻辑主机,表示一个或多个容器组和这些容器的共享资源,包共用卷、唯一的集群IP和容器运行的信息,如端口等。
Node是kubernetes的工作机器(物理机或虚拟机)。Node由master管理,可以在一个node上部署多个pod。每个node至少需要两种组件,kubelet容器运行时(docker)。kubelet是负责master和所有节点间的通信进程,管理机器上运行的pod和容器。容器运行时负责从registry拉取镜像,解包镜像并运行应用实例。
kubectl 有一系列指令管理实例的运行情况。

// 查看deployment
kubectl get deployments 
// 删除deployment,会自动删除相应pods
kubectl delete deployment <部署名> 


// 查看pods
// 查看当前namespace下的pods
kubectl get pods  
// 查看所有pods
kubectl get pods --all-namespaces 
// 查看pods的具体信息
kubectl describe pod <pod name>
//特定namespace下的,需要指定namespace
kubectl describe pod <pod name> --namespace=kube-system

// 查看pod日志
kubectl logs pod <pod name>
// 进入pod
kubectl exec -it <pod name> bash

发布应用

通过上面部署的应用,在主机外访问不了(可以通过kubectl proxy的方式访问)。还要需要将其发布为service。 service是一组相同逻辑的pods和一个访问它们的策略。一组pods通常由label选择器确定。可以在ServiceSpec 中指定类型以不同的方式暴露服务。

  • Cluster IP(默认): 只有集群内部访问。
  • NodePort: 使用NAT方式,在集群中每个选定的节点的同一端口上暴露服务。可以在集群外部访问服务。
  • LoadBalancer:创建外部负载均衡。
  • ExternalName:使用任意名称显示服务。

可以通过kubectl expose命令来发布服务。

$ kubectl expose deployment/<deployment name> --type=NodePort --port 1226

这样就expose了一个服务,就可以外部访问了。可以通过一下指令得到url。

$ minikube service <service name> --url
http://192.168.64.2:32354

$ curl http://192.168.64.2:32354
{"success":true,"msg":"hello kubernetes"}

扩容和更新

根据线上需求,扩容和缩容是常会遇到的问题。Scaling 是通过更改 Deployment 中的副本数量实现的。一旦您有应用程序的多个实例,您将能够滚动更新,而不会停止服务。通过kubectl scale指令来扩容和缩容。

// 扩充到3个实例
kubectl scale deployments/docker-demo --replicas=3
// 缩小到1个实例
kubectl scale deployments/docker-demo --replicas=1

我们都是期望应用24/7运行,但又需要频繁部署。这我们就需要rolling update(滚动更新)。 Rolling updates 允许通过使用新的 Pods 实例逐个更新来实现零停机的部署更新。新的 Pods 会被调度到可用资源的 Node 节点上。可以通过set image修改镜像。

$ kubectl set image deployments/<部署名> <部署名>=镜像名:tag

如我们的第二版镜像。

kubectl set image deployments/docker-demo docker-demo=dennisge/docker_demo:v2

重新设置镜像之后,就会执行滚动更新。

以上我们就成功部署了自己的应用。但是都是通过指令来进行的,下面我们将介绍一下部署kubernetes dashboard,并通过web ui的方式部署、删除和修改相关应用等。

kubenetes dashboard

Kubernetes dashboard是一个基于web UI的用户接口,可以再界面上完成上面的部署应用、扩容缩容和滚动更新等一系列操作,也可以查看cluster的运行情况。
如果没GFW的原因,minikube会下载和部署一些组件的,运行在 kube-system namespace 下。可以查看镜像看看这些组件。

$ docker images
REPOSITORY                                             TAG                 IMAGE ID            CREATED             SIZE

gcr.io/google_containers/k8s-dns-kube-dns-amd64        1.14.4              a8e00546bcf3        5 weeks ago         49.4MB
gcr.io/google_containers/k8s-dns-dnsmasq-nanny-amd64   1.14.4              f7f45b9cb733        5 weeks ago         41.4MB
gcr.io/google-containers/kube-addon-manager            v6.4-beta.2         0a951668696f        7 weeks ago         79.2MB
gcr.io/google_containers/kubernetes-dashboard-amd64    v1.6.1              71dfe833ce74        2 months ago        134MB
gcr.io/google_containers/pause-amd64                   3.0                 99e59f495ffa        15 months ago       747kB

安装好之后,就可以通过以下指令获得dashboard的访问地址。

$ minikube dashboard --url

这样就可以访问dashboard,并进行相关的操作。
但这里通常会遇到dashboard 和 kube-addon-manager出现ImagePullBackOff的情况。这个问题就是我们无法拉取google的镜像。可以参考这个方法来解决。
这个方法主要是冒充原来的镜像。可以查看对应deployment的镜像,然后在别的地方如阿里镜像等拉取对应镜像,或者拉取镜像放到docker hub。再登陆Minikube,拉取该镜像,再重新tag为原deployment需要的镜像。
以kube-addon-manager 为例。

$ minikube ssh
$ docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/kube-addon-manager-amd64:v6.1
$ docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/kube-addon-manager-amd64:v6.1 gcr.io/google-containers/kube-addon-manager:v6.1

这样就可以看到这些组件的状态变为running。
这样的dashboard还是很朴素,如下图:

kubernetes-dashboard

这就需要安装heapster插件。可以查看现在的addons 列表。

$ minikube addons list
- registry: disabled
- dashboard: enabled
- kube-dns: enabled
- heapster: disabled
- ingress: disabled
- addon-manager: enabled
- default-storageclass: enabled
- registry-creds: disabled

发现heapster是关闭的。可以把它开启。

$ minikube addons enable heapter

我们会看到已经部署了相关的pod。

$ kubectl get pods --namespace=kube-system
NAME                          READY     STATUS             RESTARTS   AGE
heapster-jgzf2                1/1       Running            0          12h
influxdb-grafana-lqdtk        2/2       Running            0          12h

这通常会遇到ImagePullBackOff 问题,无法拉取镜像。这里需要以下几个镜像。

gcr.io/google_containers/heapster_influxdb:v0.6
gcr.io/google_containers/heapster_grafana:v2.6.0-2
gcr.io/google_containers/heapster_influxdb:v0.6

可以安装上面介绍的镜像冒充的方法解决这个问题。我是在我的aws上下载相应镜像push到我的docker hub。然后下载docker hub的镜像,再重命名需要的镜像。这样我们的heapster就正常运行了,可以看到漂亮的图表。
就可以愉快的使用dashboard了。

kubernetes-dashboard-heapster

总结

Kubernetes是docker的重要编排工具。我们通过Minikube来部署一个应用,来了解kubernetes的一些基本使用。
本文介绍了在mac和linux的Minikube安装、kubectl安装,以及基于VM来启动minikube集群。然后通过部署一个node应用,并成功地伸缩扩容并通过rolling update更新镜像。在这个过程中,我们也了解了node、deployment、pod、service等概念。最后我们介绍了一下kubernetes dashboard的安装、heapster插件安装以及可能遇到的问题及解决方案。

参考

[1] kubernetes tutorial
[2] 利用 Minikube 运行 Kubernetes
[3] Driver plugin installation
[4] minikube github
[5] mac安装kubernetes并运行echoserver
[6]Heapster Addon

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.