Paul's Notes

Experience, Record, Share.

Docker Practice: Storage

| Comments

Docker基础

简介

Docker作为著名的开源容器引擎,在业界有着广泛的使用。 由于使用了现代Linux内核高级特性(如cgroup,namespace),它可以更高效更安全地使用宿主机的计算、存储、网络等资源。 容器技术作为轻量级的虚拟化技术,相比传统的基于Hypervisor和VM的虚拟化更适合大规模的横向扩展。 容器技术独立与上层应用语言和底层操作系统,将代码和其所依赖的环境整体打包,实现了“构建一次,处处运行”的目标。 在构建应用方面,容器技术可以大大提升运维工作的效率,缩短应用开发上线发布周期,甚至重新定义了软件开发、测试、交付和部署的流程。

本文作为Docker的初级实践总结,描述了Docker的安装及基本命令,并以RedisPostgreSQL的服务部署为例,记录Docker官方镜像服务部署的过程。

安装

在ubuntu 14.04下安装参考安装文档,过程如下:

1
2
3
4
sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
sudo su -c "echo 'deb https://apt.dockerproject.org/repo ubuntu-trusty main' > /etc/apt/sources.list.d/docker.list"
sudo apt-get update
sudo apt-get install docker-engine

免sudo执行docker

将指定用户加入docker组,并重新登录

1
sudo usermod -aG docker $USER

更改docker默认数据目录

更改/etc/default/docker

1
DOCKER_OPTS="-g /data/var/lib/docker"

重启服务

1
sudo service docker restart

基本操作

常用启动容器实例模式

后台执行容器 -d

1
docker run -d ubuntu /bin/bash -c "vmstat 5"  # docker logs -f 查看输出

执行完命令即时删除 –rm

1
docker run --rm ubuntu /bin/bash -c "uname -a"

交互式 -it

1
2
docker run -it ubuntu /bin/bash   # 操作完成后容器停止,可以start,再attach
docker exec -it CONTAINER_NAME /bin/bash # shell到后台运行的容器

查看子命令使用: docker help inspect

列出所有运行的容器列表: docker ps

删除所有容器: docker rm $(docker ps -aq)

搜索镜像: https://hub.docker.com/

Redis服务部署

Redis是开源的内存数据结构存储引擎,常用来实现缓存服务、键值存储引擎以及Pub/Sub引擎等。用Docker部署Redis服务非常便捷。

1
docker run --name myapp-redis -p 127.0.0.1:49514:6379 -d redis

Docker会做以下工作:

在本地需找官方redis官方镜像,如果没有则下载;用官方镜像启动名为myapp-redis的容器实例;将实例开放的默认redis服务6379端口映射到宿主系统的本地49514端口。

查看redis服务的日志

1
docker logs -f myapp-redis

验证:通过宿主系统的redis客户端访问

1
redis-cli -p 49514

PostgreSQL服务部署

PostgreSQL是开源的关系型数据库。用Docker部署PostgreSQL服务同样直观便捷。

1
2
3
4
docker run --name myapp-postgres -p 127.0.0.1:49513:5432    \
-v $(pwd)/pg-data:/var/lib/postgresql/data                  \
-e POSTGRES_PASSWORD=postgres                               \
-d postgres

同样的,docker会下载postgresql官方镜像,启动myapp-postgres实例,并将服务端口映射到宿主机的49513端口。

注意这里用到-v参数,可以为容器的volume(数据卷)指定映射目录。 官方的postgresql镜像将/var/lib/postgresql/data/目录作为volume处理,使得单独存储数据变得简单。 可以简单的理解为将$(pwd)/pg-data作为设备挂载到容器的/var/lib/postgresql/data/目录。这样,备份和迁移都很方便,不依赖容器。

-e参数为创建容器时指定的环境变量,这里将postrgres超级用户的密码设置为postgres。

然后便可以在宿主机通过posgresql的客户端(如psql、createuser、createdb)访问服务了。

1
2
3
4
5
6
# 以postgres超级用户测试数据连接
PGPASSWORD=postgres psql -h localhost -p 49513 -U postgres -e
# 创建用户myapp,并设置访问权限(可以创建角色和数据库)及密码
PGPASSWORD=postgres createuser -h localhost -p 49513 -U postgres -rdeP myapp
# 以新用户创建同名数据库
PGPASSWORD=myapp-pass createdb -h localhost -p 49513 -U myapp -e myapp

官方镜像还支持若干个环境变量,如初始superuser的用户名及密码等等。以下一个命令即可完成以上流程。

1
2
3
4
docker run --name myapp-postgres -p 127.0.0.1:49513:5432    \
-v $(pwd)/pg-data:/var/lib/postgresql/data                  \
-e POSTGRES_PASSWORD=myapp-pass -e POSTGRES_USER=myapp      \
-d postgres

总结

通过以上两个例子,创建了两个容器实例,分别是redis服务和有分离数据卷的postgresql服务。 通过应用Docker容器技术,最核心的是简化测试交付部署流程,并使这些流程更容易地实现自动化。 从而使开发者更多地专注于业务逻辑,加速其价值提升。

Class Inheritance From ES5 to ES6

| Comments

Test Case

For test-driven development, the test case always comes first. The Phone inherits the Node.js EventEmitter: a phone is a customized event emitter, only augmented with its own properties and methods.

A phone has its own name, can powerOn itself. Since it’s an event emitter, it can emit ‘income’ event, and can set the handler by addListener as event emitter.

test.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var EventEmitter = require('events').EventEmitter;

//var Phone = require('./phone-es5');
//var Phone = require('./phone-util');
var Phone = require('./phone-es6');   // all implementation should have same interface

var p1 = new Phone('p1');             // class instantiation with parameter
p1.powerOn();                         // invoke instance methodl

console.log(p1 instanceof Phone);     // test ISA object of super class
console.log(p1 instanceof EventEmitter);
console.log(p1);                      // print instance at run-time

var incomeListener = function(number) {
    console.log('incomming call from: ' + number);
};

p1.addListener('income', incomeListener);
p1.income('+4478123456');             // invoke instance method
Phone.vendor('xiaomi');               // static/class method

Expect the following desired output:

1
2
3
4
5
6
7
8
9
10
11
[Phone instance method] p1 is powered on.
true
true
Phone {
  domain: null,
  _events: {},
  _eventsCount: 0,
  _maxListeners: 20,
  name: 'p1' }
incomming call from: +4478123456
[Phone static method] vendor: xiaomi

Prototype-Based Inheritance in ES5

In fact, javascript object is prototype-based, there isn’t class at all, everything in javascript is object. There’s only Object Extension (extends an exists object to a new object), rather than Class Inheritance (create new subclass that inherits the parent class). To implement Inheritance is to build/extend the appropriate prototype chain. The prototype-based inheritance is more flexible, and it’s easy to emulate traditional textbook or java-like class or inheritance.

phone-es5.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var EventEmitter = require('events').EventEmitter;
function Phone(name) {
  EventEmitter.call(this);    // call parent's constructor
  this.setMaxListeners(20);   // customize with parent's method
  this.name = name;           // self field
};
Phone.prototype = new EventEmitter();
// same as: Phone.prototype = Object.create(EventEmitter.prototype);
Phone.prototype.constructor = Phone;
Phone.prototype.powerOn = function() {
  console.log('[Phone instance method] ' + this.name + ' is powered on.');
};
Phone.prototype.income = function(number) {
  this.emit('income', number);
};
Phone.vendor = function(name) {
  console.log('[Phone static method] vendor: ' + name);
}
module.exports = Phone;

Using Node.js util.inherits

Node.js util module provides syntactical sugar for easily implemention of class-like inheritance.

phone-util.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var EventEmitter = require('events').EventEmitter;
var util = require('util');
function Phone(name) {
  EventEmitter.call(this);    // call parent's constructor
  this.setMaxListeners(20);   // customize with parent's method
  this.name = name;           // self field
};
util.inherits(Phone, EventEmitter);
Phone.prototype.powerOn = function() {
  console.log('[Phone instance method] ' + this.name + ' is powered on.');
};
Phone.prototype.income = function(number) {
  this.emit('income', number);
};
Phone.vendor = function(name) {
  console.log('[Phone static method] vendor: ' + name);
}
module.exports = Phone;

util.inherits levarages Object.create() of ES5, the inherits line equals to:

1
2
3
4
5
6
7
8
9
Phone.super_ = EventEmitter;
Phone.prototype = Object.create(EventEmitter.prototype, {
  constructor: {
    value: Phone,
    enumerable: false,
    writable: true,
    configurable: true
  }
});

Class Syntax of ES6

From node-v4.0, lots of ES6 features are implemented by default(shipping). Such as block scoping, collections, promises and Arrow Functions. And Class syntax is also fully supported. But, note that class is just another syntactical sugar:

JavaScript classes are introduced in ECMAScript 6 and are syntactical sugar over JavaScript's existing prototype-based inheritance.
The class syntax is not introducing a new object-oriented inheritance model to JavaScript.
JavaScript classes provide a much simpler and clearer syntax to create objects and deal with inheritance.

With class syntax, class/inheritance can be implemented in a more straightforward manner.

phone-es6.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
'use strict'
var EventEmitter = require('events').EventEmitter

class Phone extends EventEmitter {
  constructor(name) {
    super()
    this.setMaxListeners(20)
    this.name = name
  }
  powerOn() {
    console.log('[Phone instance method] ' + this.name + ' is powered on.')
  }
  income(number) {
    this.emit('income', number)
  }
  static vendor(name) {
    console.log(`[Phone static method] vendor: ${name}`)
  }
}

module.exports = Phone

Note: ‘use strict’ is required, and we use the template string syntax in static method (class method). And class declaration (as well as let, const, and function in strict mode) cannot be hoisted, unlike var and function.

In summary, with ES6 support in Node.js v4.0, class inheritance has never been more easier. So, let’s embrace ES6.

SSH Port Forwarding

| Comments

Overview

利用SSH端口转发,可以方便实现各类翻墙打洞需求。本文详细介绍SSH端口转发的三种用法,以及其所适用的场景。

Environment

  • L0: localhost behind NAT, with lan ip 192.168.0.100
  • L1: host within same lan of L0, with lan ip 192.168.0.101
  • R0: remote host (cloud vps) with private ip 10.0.0.100
  • R1: remote host (cloud vps) with private ip 10.0.0.101

L0 can ssh (default port 22) to R0 by its public domain name r0.example.com with public key, like this:

1
ssh user@r0.example.com

Forwarding local host (-L)

Usage

Specifies that the given port on the local (client) host is to be forwarded to the given host and port on the remote side.

SSH -L is good for exposing a remote port locally.

增强本地主机的访问远程主机(局域网)能力。

Example

1. Forward L0 to R0

The mongod has the http web service, listening only on localhost:28017. With the following command:

1
ssh -NL 28018:localhost:28017 user@r0.example.com

Then the remote service on 28017 will be accessed on L0’s 28018 port from L0.

2. Forward L0 to R1

Suggest that there’s an API server on R1, listening only on port 10030 of lan (10.0.0.0/24). Because R0 can access R1’s private address of the same lan, so the following command will make R1’s service of port 10030 accessible from L0’s local port 10031.

1
ssh -NL 10031:10.0.0.101:10030 user@r0.example.com

Note, use R1’s private ip 10.0.0.101 instead of localhost.

3. Forward L1 to R1

Say R0 can access R1 through ssh command: ssh secret-user@10.0.0.101, then the following command

1
ssh -NL 192.168.0.100:2222:10.0.0.101:22 user@r0.example.com

will forward L0’s port 2222 to the R1’s port 22, and even make L0 listening within the lan. So from L1, we can access to R1 by this command:

1
ssh -p 2222 secret-user@192.168.0.100

Awesome!

Forwarding remote host (-R)

Usage

Specifies that the given port on the remote (server) host is to be forwarded to the given host and port on the local side.

So the SSH -R can be useful when accessing a box hidden behind a NAT.

增强远端主机的访问本地主机(局域网)的能力。

Example

1. Forward R0 to L0

1
ssh -NR 2222:localhost:22 user@r0.example.com

2. Forward R0 to L1

1
ssh -NR 2222:192.168.0.101:22 user@r0.example.com

3. Forward R1 to L1

Unlike local forwarding, remote forwarding from R1 to L1 is not permitted by default due to security policy, unless we change sshd_config file on R0, with the additional config line:

1
GatewayPorts = yes

then the ultimate tunnel created, with which we can access L1’s port on machine R1. Cool?

Dynamic forwarding (-D)

Usage

Specifies a local “dynamic” application-level port forwarding...and ssh will act as a SOCKS server.

Example

1
ssh -ND 1080 user@r0.example.com

Then, there exists a SOCKS server on L0, and we can use it as a proxy server.

1
curl -x socks5h://localhost:1080 https://www.google.com

Shadowsocks Tutorial

| Comments

Overview

socks5

Socket Secure (SOCKS) is an Internet protocol that routes network packets between a client and server through a proxy server. SOCKS5 additionally provides authentication so only authorized users may access a server.

Practically, a SOCKS server proxies TCP connections to an arbitrary IP address, and provides a means for UDP packets to be forwarded.SOCKS operates at a lower level than HTTP proxying: SOCKS uses a handshake protocol to inform the proxy software about the connection that the client is trying to make, and then acts as transparently as possible. SOCKS proxies can also forward UDP traffic and work in reverse.

shadowsocks

Shadowsocks is a fast tunnel proxy that can help bypass firewalls.

test environment

Remote VPS OS: Ubuntu-14.04 Server VPS on DigitalOcean

Local Client OS: Ubuntu-14.04 Desktop

Installation

install package

1
2
sudo apt-get install python-pip
sudo pip install shadowsocks

remote server

1
ssserver -k PASSWORD -d start

This will run shadowsocks remote server listening at 0.0.0.0:8388

local server

1
sslocal -s VPS -k PASSWORD

This will run shadowsocks local server listening at 127.0.0.1:1080

Client Configuration

Web Browser

Thanks for proxy setting extension like FoxyProxy in Firefox, SwitchyOmega in Chrome, socks proxy tunnel can be easily configured.

Choose socks v5 protocal at localhost:1080, and get it work.

Curl

1
curl -x socks5h://localhost https://www.google.com

Note that we specify protocal as socks5h instead of socks5, which means we use the specified SOCKS5 proxy, and let the proxy resolve the host name, otherwise it may not resolve hostname. And port 1080 is by default.

Nodejs Request

request utility doesn’t support socks proxy by default, so we need socks5-http-client and socks5-https-client to create agent.

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
var request = require('request');
var url = require('url');
var socks5HttpAgent = require('socks5-http-client/lib/Agent');
var socks5HttpsAgent = require('socks5-https-client/lib/Agent');

var urlString = process.argv[2];
var url = url.parse(urlString);
var agentClass = (url.protocol === 'https:') ? socks5HttpsAgent : socks5HttpAgent;
var agentOptions = {
  socksHost: 'localhost', // defaults to localhost
  socksPort: 1080         // defaults to 1080
};
var options = {
  uri: url,
  agentClass: agentClass,
  agentOptions: agentOptions,
  method: 'GET'
};
request(options, function(err, res, body) {
  if (err) console.log(err.message);
  else {
    console.log(res.statusCode);
    console.log(body);
  }
});

Finally, test it.

1
2
node socks-proxy.js http://www.google.com
node socks-proxy.js https://www.google.com

Session With Express and Nginx

| Comments

为什么一定要持久化存储会话信息

如果会话信息仅仅存储在内存…

  • 服务器重启,用户将会登出
  • 无法在多个node进程(或集群环境)中共享会话

所以,会话(无论是HTTP会话还是Web-Socket会话)都需要在服务器端持久化存储在数据库中,如Redis/Mongo。

环境: Express 4.12.2, Nginx 1.8.0

Express添加Redis会话存储

需要的相关库或组件:

1
2
3
4
5
6
7
8
9
10
11
var http = require('http');
var socketio = require('socket.io');
var session = require('express-session');           // 会话支持
var passport = require('passport');                 // 身份认证,依赖express-session
var RedisStore = require('connect-redis')(session); // redis存储,用于持久化会话
var RedisAdapter = require('socket.io-redis');      // 存储web-socket的连接
var mongoose = require('mongoose');
// ...
var app = express();
var server = http.Server(app);
var sio = socketio(server);

配置Redis会话存储HTTP会话

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var sessionMiddleware = session({
  name: 'XXX:sess',
  secret: 'XXX',
  proxy: true,
  resave: false,
  store: new RedisStore(),
  saveUninitialized: false,
  cookie: {
    //secure: true, // only https
    maxAge: 7*24*60*60*1000 // 1 week
  }
});
// 应用会话存储中间件
app.use(sessionMiddleware);
// passport登录认证
app.use(passport.initialize());
app.use(passport.session());

配置Redis会话存储Web-Socket会话

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sio.adapter(RedisAdapter({host: 'localhost', port: 6379}));
// 为socket server添加redis存储的中间件
sio.use(function(socket, next) {
  sessionMiddleware(socket.request, socket.request.res, next);
});
// 对未登录的用户禁用socket支持
sio.use(function(socket, next){
  var passport = socket.request.session.passport;
  if (!passport || !passport.user) {
    logger.debug('no socket support without login');
    next(new Error('Authentication error'));
  }
  else next();
});

Nginx配置支持负载均衡

Web-Socket的连接建立需要同一个tcp连接上的多次握手支持,要求比一般的HTTP会话高一些。 所以为实现多进程的共享会话支持,需要配置支持iphash的upstream。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
upstream nodes {
  ip_hash;
  server localhost:8001;
  server localhost:8002;
  server localhost:8003;
  server localhost:8004;
  keepalive 512;
}

server{
  listen 443 ssl;
  server_name example.com;
  location / {
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $host;
    proxy_http_version 1.1;
    proxy_pass http://nodes;
  }
}

注意,可以用pm2启动多个node服务进程,但必须以fork mode启动,而不是-i的集群方式启动,负载均衡完全交给nginx即可。 如此:

1
2
3
4
pm2 start -f --name web1 app.js -- 8001
pm2 start -f --name web2 app.js -- 8002
pm2 start -f --name web3 app.js -- 8003
pm2 start -f --name web4 app.js -- 8004

参考资料

Socket.io doc: using multiple nodes

Installing Memcache With PHP

| Comments

install memcached

OS: Ubuntu-14.04 server

PHP: 5.6.8 source build

install memcached with apt-get

1
sudo apt-get install memcached

verification

1
echo "stats settings" | nc localhost 11211

install memcache php extension

download source from pecl

1
wget http://pecl.php.net/get/memcache-2.2.7.tgz

install with pecl

1
/path/to/php/bin/pecl install /path/to/memcache-2.2.7.tgz

There exsists the lib/php/extensions/no-debug-non-zts-XXXXXXX/memcache.so

update the php.ini

add “extension=memcache.so” to php.ini

reload php-fpm server

1
kill -USR2 $(cat /path/to/php/var/run/php-fpm.pid) # no need sudo

Check the php.info page, make sure that memcache section exists

Blog Writing With Octopress 3

| Comments

initialize workspace

1
2
3
4
5
  git clone git@github.com:Pro-YY/pro-yy.github.io.git -b source
  cd pro-yy.github.io/
  git clone git@github.com:Pro-YY/pro-yy.github.io.git -b master _deploy
  apt install ruby-bundler ruby-dev
  bundle install

write post

1
2
  rake new_post["Blog Writing with Octopress 3"]
  vim source/_posts/2015-07-15-blog-writing-with-octopress-3.markdown

preview

1
2
  rake generate
  rake preview

preview at http://localhost:4000

deploy

1
  rake deploy

commit source

1
2
  git commit -asm 'new post'
  git push

update octopress

1
2
3
4
5
  git remote add octopress git://github.com/imathis/octopress.git
  git pull octopress master
  bundle install
  rake update_source
  rake update_style

category list/cloud

plugin from https://github.com/alswl/octopress-category-list