Feb 6, 2016 - Hyper 0.5 Released

Comments

今天上午,我 Tag 了 Hyper, runV, Hyperstart 三个项目的 v0.5.0 标签,发布了 0.5 版本,Release Note 是英文的,想着也该有个中文的发布声明,就动笔写了这篇 Blog,送给从此刻开始的春节假期,祝各位开发者们节日休息的好、运维们节日别接告警。

从 0.4 到 0.5,经历了接近五个月,与之前的发布周期相比是长了些,但这五个月中,我们并没有闲着,作为一个开源项目,大家从 GitHub 项目的变更记录可以看到,我们一直在改进自己。

同时,在这期间,我们还在去年10月末和11月初接连参加了 OpenStack Summit Tokyo 和 DockerCon EU。在会上,我们向世界宣告了我们的新项目——Hypernetes (曾用名 HyperStack)。这个项目以 Hyper 作为强隔离性的运行时;使用 Kubernetes 作为 Pod 调度引擎;利用 OpenStack 组件,以 Neutron 作为 SDN 解决方案,以 Cinder/Ceph 作为持久存储方案,也用 Keystone 作为认证授权中心。这些特性使 Hypernetes 成为一个多租户的 Kubernetes 发布版,吸引了诸多关注目光。

这里简单介绍一下 0.4 到 0.5 的变化,最明显的可能就是,对于 CentOS 和 Fedora 的用户,你们现在可以直接安装 RPM 包了(包好,包好……),呃……为啥没有 deb?作为13年的 Debianer,我也很抱歉啊,其实,事实是,CentOS 7.x 的依赖需求情况比较特殊,所以我们专门 Build 了 RPM,当然,稍后我们也会提供 Deb 的,放心吧。

从软件本身来说,最主要的是我们对 Hyper 的行为做了很多修复和改进,比如对runexec 命令的返回值、运行时 tty 的行为、文件映射的行为、文件插入行为的完善和修复、以及很多稳定性的改进,测试方面也做了加强。在功能方面,主要的改进在于和 Neutron 的集成、和 Cinder 的 ceph volume 的集成等,细节方面,run 命令增加了 -t 参数标记是否有 tty 设备的区别,增加了 logs 命令,再就是增加了 libvirt driver,用户可以在 libvirt 中监控到 hyper 创建的虚拟机。

最近也有一些译文把 Hypernetes 介绍给国内的朋友,感谢诸位的关注,是的,我们是一个由国内发起、开发团队主要在国内的开源项目。在此版本发布,也是春节来临之际,感谢开发团队们的努力工作,虽然大部分开发团队都是远程工作的,但这就是开源社区的一贯工作方式,我们的团队完全胜任这个工作方式,生产效率与集中工作相比好不示弱。

0.5 版本的发布让 Hyper 更加接近了产品化,在接下来的时间里 Hyper 和 Hypernetes 都不会停下来,请大家期待我们不久后的下一次产品发布。谢谢大家!再祝各位春节快乐、少收报警!

Jan 23, 2016 - 关于 Unikernel,你注意到或没注意到的一些东西

Comments

利益相关(强行植入广告):我们(Hyper)创业团队(也是开源项目)同样做了一种基于 Hypervisor 技术的容器解决方案,然而与 unikernel 不同的是,我们是面向通用的容器镜像的,可以直接运行已有镜像。在我看来,大部分人在热捧 unikernel 的时候,实际上他需要的是 Hyper 这样的技术。

免责声明:我所做的预测只对我自己负责,我们在用创造未来的方式来预测未来,并且有其他人与我们在一同来创造未来,我们坚信我们的预测是正确的。

作为一个概念的 unikernel

新闻背景

前两天,Docker 宣布收购了 Unikernel Systems,引起了朋友圈刷屏(比如这个这个)。不过 Twitter 上还是有一些批评声音的,而且 Joyent 的 Bryan Cantrill 大神亲自向信众解释了为啥 unikernel 不适合于生产系统,当然,在存在利益相关这方面,他和我是一样的。

在和一些技术圈的朋友讨论后,我发现有的朋友并不是很了解 unikernel 技术,只是听起来觉得很好。于是,昨天我在朋友圈里写了这么一段话

事实上,unikernel 和 docker image 都是对应用的一个静态封装,从很多种意义上说,unikernel 都是一个更精巧的封装,而 docker image 则是一种更简单、松散、或者说更通用的封装。对于每个人是不是有意义,这因人而异,历史上精巧专用的方案被简单而更具兼容性的方案打败的例子比比皆是。这里,很多人都忽视的一点就是,从 unikernel 的定义出发,就不存在一个 unikernel 可以适应多样的已有二进制程序。需要好好审视自己的应用场景,才知道这张旧船票能否登上你的客船

现在我在 Blog 里继续解释一下。

插一句题外话, unikernel 是一类技术,并不只有 Unikernel Systems 一家拥有,有兴趣的同学可以看看 OSV,这个团队来自 Red Hat,头领是 KVM 的作者 Avi,是一个非常优秀而有执行力的团队。

什么是 unikernel

根据 Wiki 定义,unikernel 是专用的、单一地址空间,将操作系统功能作为库调用的程序镜像。也就是说,做一个 unikernel 应用,可以不需要复杂、完整的操作系统,可以只包含需要的功能。想想这简直是小而美的微服务架构的终极追求。

Docker 镜像之所以为人们所称道的原因就在于它完整封装了应用环境,使得应用可以在不同的环境里无需修改直接运行,不用考虑 distro、依赖库之类的问题,而这里,unikernel 连 kernel 都一并打包了,只要放在 hypervisor 上即可运行,这种 portability 似乎是无与伦比的,另一方面,hypervisor 带来的隔离效果要比 linux container 更好,而且,去掉不必要功能后,unikernel 包含的可能含有漏洞的代码也会少,从安全的角度讲,你做得越少,你做错的也就越少,人们不禁赞叹,这真是终极的解决之道啊。

不过,等等,这里似乎有点问题啊。单一地址空间、操作系统作为库,这样的话和传统的程序有点不一样啊,程序不是跑在 Linux kernel 上了,那么多进程调度谁来做?已有的程序和库怎么用呢?是的,这就是我所说的不存在可以直接适配多样的已有二进制程序的 unikernel,你需要为每个程序一切从头做起,做自己的 unikernel app。换一个角度说,实际上 unikernel 不是让你把你的已有程序和它依赖的 kernel 功能打包,相反,是提供一套工具,帮你把你的程序嵌入到内核里去,得到一个你自己的专有的内核,这要从源码出发,甚至进行一定的移植工作。

所以,当我在看到这个新闻的时候,甚至在想,docker 这是要回到 PaaS 上去,放弃统一镜像,转而做源码级的服务么?

通用的 unikernel?

看到我前面的论述,有人会立刻说,把 init 程序,比如 systemd 甚至 Hyper 的 hyperstart 和 unikernel 打包,然后不就可以直接接入已有二进制程序直接,做成通用的 unikernel 了么?

可是,仔细看看啊各位,首先,从 API/ABI 的角度看,通用的 Unix 或者 Linux kernel 是建立在完整的系统调用集的基础上的,unikernel 通过将内核代码与 App 代码的耦合,放弃了对系统调用的提供。而一旦转而提供完整的 ABI 给应用,那么,和 Linux kernel 也就没有区别了。所以说,unikernel 在提供通用性和进行应用边界划分的时候,选择的不是一个更加普适的接口。或者说,当它转而通用地支持已有的二进制程序的时候,那么它重新变成 hyper 的解决方案,而不是 unikernel 了。

Unikernel 化一个应用,你面对的问题

除了丧失二进制级的应用通用性之外,Unikernel 其实还面临着很多其他的问题:

  • 首先,你需要移植程序,相当于支持一个新的平台。各种 unikernel 技术多少都提供了和 libc 或 java 平台或其他语言环境一致性非常高的开发环境,方便用户的移植,然而,移植工作多少还是需要的。
  • 其次,如果你的应用本身是多线程或多进程的,需要考虑单一地址空间的限制,这不仅是程序语言方面的限制,有的时候也是架构或语义层面的影响。
  • 最后,如 Bryan Cantrill 所说,unikernel 程序实际是完全内核态程序,而且只有单一地址空间,这根本没法 debug 嘛,传统或现代的通用程序调试设施基本上都无法使用,连进程的概念都没有,这对开发和调试带来了极大的难度,显然并不是所有的应用开发者都能驾驭这样的应用的。

Unikernel 适用的场景有哪些

那么,什么应用需要这个场景呢?我个人认为,只有极端追求性能和小体积的场所,可以不惜代价地去做这种 unikernel,就此,很多人都觉得 IoT (物联网)似乎是最佳应用场景之一。我只想问,我们早年间在 4KB ROM,128B RAM 的限制下写的 51 单片机程序在算不算 unikernel 程序?

你真的想要的是什么?

感谢各位看到这里,作为一篇到处植入广告的文章,能看到最后的都是真爱,所以,我就再插入一段广告吧。如果你的容器服务跑在可信任的环境里,性能是首要要求,那么基于 Linux Container 的 Docker 是你的首选;而如果要提供多租户的容器服务,或者容器里的应用不被完全信任(比如用于测试等用途),那么,基于 Hypervisor 的 Hyper 完全胜任这个环境。并且,两者使用完全相同的镜像,对大部分镜像来说(除了那些刻意放弃主机和容器之间隔离性的应用),Container 能跑的,Hypervisor 也同样能跑。

Dec 27, 2015 - DIY PM 2.5 监测仪 (1): 采集和显示

Comments

如今北京的空气,嗯,关注点空气质量自我保护吧,废话不多说了,开工吧。

基本原理

有一定精确性但成本比较低的颗粒物检测方法差不多都用的是同一个原理——由于细颗粒物的直径(微米级)和光的波长(可见光的波长是 0.39-0.7 微米)比较接近,所以细颗粒物会对光产生比较强烈的散射,这也就是 PM 2.5 高的时候,空气的能见度一定不好的原因。当然,PM 2.5 高对能见度不好是充分非必要条件,其他因素也可能导致空气能见度不佳。

基于这个原理,发射一束激光,在一定角度上观测散射光的强度,就可以判断出颗粒物的浓度,当然,这个方法要求对空气中颗粒物直径分布模型、产生散射的物质的成分等有一个假设,同时还受限于光强度测量的精确性,因此,这个测量的精确度是受限的,对于科研可能不太够,但对日常生活的空气检测来说,应该是比较有效和准确的。这次我们的监测仪使用的就是采用这个原理制成的一个模块。

准备材料

  • raspberry pi 或 arduino,目前测试用的是 rpi 2 和 arduino uno,订的 arduino nano 正在路上,nano 就又小又便宜了。
  • 传感器,我用的是淘宝上买的 PMS1003,大家自行搜索 PM2.5 传感器吧,关键字是“激光”或 laser。不同的传感器接口啥的可能不太一样,我这个传感器自带 UART 输出,所以可以从串口直接读取数据。
  • LCD1602 液晶显示,我用了一块 8574 转换电路,转成了 I2C 接口,这样比较省接口。可以直接买一个带 I2C 的 LCD,也可以分别买了自己连,嗯,我是分两次买自己连的,因为我最早(去年?)不知道有 I2C 的……
  • 连接线

硬件链接

因为插接件不太兼容,我把传感器的连接线一端截开焊在了一块板子上,连上插针,方便连接,如果手头有合适插接件的话,可能不用动烙铁了。具体连接很简单:

传感器连接

  • 5V 供电
  • Setting 管脚,这个接在 Pi 的一个 GPIO 接口上(比如 GPIO 17)或 Arduino 的一个接口上,比如这次用的是 IO 2。这个管脚拉高,模块处于工作状态
  • TXD 连接到 Pi 的 RXD 或 Arduino 的 RXD 上,做采集

LCD

  • SDA/SCL I2C 的数据和时钟,接在 Pi 或者 Arduino 的 数据和时钟上
  • Vcc/GND 接 5V 电源和地,实测接 3.3V 会供电不足

软件环境

Pi 的话需要准备下,要把缺省关闭的 I2C 打开,并且把 console 连接的串口去掉,空出串口来给我们采集数据用。

其他的就是要查一下 LCD 的 I2C 地址。

主体流程

  • 初始化
    • 液晶初始化:设置合适的格式和背光
    • 模块初始化:初始化串口,找到 0x42, 0x4d 作为帧的开始,一帧 32 个字节(不同模块参考相应的手册)
  • 采集
    • 采集对应字节的数据,从大结尾转为短整型,然后显示在液晶屏上

如此而已,非常简单,以后我们再考虑用蓝牙讲数据发送出来。

源码清单

Pi 和 Arduino 的源码如下:

Pi 源码

主程序:

#!/usr/bin/env python

import RPi.GPIO as GPIO
import serial
import pylcdlib
# pylcdlib from https://gist.github.com/gnawux/4f68b8e301b203489336

def readbe16(s, pos):
    return ( ord(s[pos])<<8) + ord(s[pos+1])

GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.OUT)
GPIO.output(17, True)

# define bus address, port, and pins
display = pylcdlib.lcd(0x27,1, Rs=0, Rw=1, En=2, Backlight=3, D4=4, D5=5, D6=6, D7=7)
display.lcd_clear()

ser = serial.Serial("/dev/ttyAMA0")
ser.baudrate=9600
data = ser.read(32)
pos  = 0
while True:
    pos = data[pos:].index(chr(0x42))
    if ord(data[(pos+1)&31]) == 0x4d:
        print('found BM at %d' % (pos))
        ser.read(pos)
	break
    pos = pos + 1
    if pos > 31:
        print('frame format error')
        exit(1)

l1d=-1
l2d=-1

while True:
    data = ser.read(32)
    if data[0] != 'B' or data[1] != 'M':
        print('frame format error while reading data: %s, %s' % (data[:2], data))
        exit(1)
    length = readbe16(data, 2)
    if length != 28:
        print('length error : %d' % (length))
        exit(1)

    pm25std = readbe16(data, 6)
    pm25atm = readbe16(data, 12)

    if l1d != pm25std:
        l1d = pm25std
        display.lcd_puts('std: %d ug/m3' % l1d,1)
    if l2d != pm25atm:
        l2d = pm25atm
        display.lcd_puts('atm: %d ug/m3'% l2d,2)

LCD显示库

#!/usr/bin/env python
"""
original from http://www.rpiblog.com/2012/07/interfacing-16x2-lcd-with-raspberry-pi.html
I modified it, thus you can set customized pin defines, such as
   lcd(0x27,1, Rs=0, Rw=1, En=2, Backlight=3, D4=4, D5=5, D6=6, D7=7)

   address 0x27
   port    1

   P0 - Rs
   P1 - RW
   P2 - En
   P3 - Backlight
   P4-P7 - D4-D7
"""
import smbus
from time import  *

#  General  i2c  device  class  so  that  other  devices  can  be  added  easily
class i2c_device:
  def __init__(self, addr, port):
    self.addr = addr
    self.bus = smbus.SMBus(port)

  def write(self, byte):
    self.bus.write_byte(self.addr, byte)

  def read(self):
    return self.bus.read_byte(self.addr)

  def read_nbytes_data(self, data, n):  #  For  sequential  reads  >  1  byte
    return self.bus.read_i2c_block_data(self.addr,  data,  n)


class  lcd:
  #initializes  objects  and  lcd
  def  __init__(self, addr, port, **kwarg):
    self.pin = {}
    self.lcd_device = i2c_device(addr, port)
    self.config(**kwarg)
    self.Backlight = True
    self.low_level_write(En=False, Rs=False, Rw=False, data=3)
    self.lcd_strobe()
    sleep(0.0005)
    self.lcd_strobe()
    sleep(0.0005)
    self.lcd_strobe()
    sleep(0.0005)
    self.low_level_write(En=False, Rs=False, Rw=False, data=2)
    self.lcd_strobe()
    sleep(0.0005)

    self.lcd_write(0x28)
    self.lcd_write(0x08)
    self.lcd_write(0x01)
    self.lcd_write(0x06)
    self.lcd_write(0x0C)
    self.lcd_write(0x0F)

  def config(self, **kwarg):
    self.pin['Rs'] = kwarg.get('Rs', 4)
    self.pin['Rw'] = kwarg.get('Rw', 5)
    self.pin['En'] = kwarg.get('En', 6)
    self.pin['Backlight'] = kwarg.get('Backlight', 7)
    self.pin['D4'] = kwarg.get('D4', 0)
    self.pin['D5'] = kwarg.get('D5', 1)
    self.pin['D6'] = kwarg.get('D6', 2)
    self.pin['D7'] = kwarg.get('D7', 3)

  def backlight(self, on=True):
    self.backlight = on

  def low_level_write(self, En, Rs, Rw, data):
    byte = 0
    if En:
      byte = byte | 1 << self.pin['En']
    if Rs:
      byte = byte | 1 << self.pin['Rs']
    if Rw:
      byte = byte | 1 << self.pin['Rw']
    if self.backlight:
      byte = byte | 1 << self.pin['Backlight']
    if data & 0x01 :
      byte = byte | 1 << self.pin['D4']
    if data & 0x02 :
      byte = byte | 1 << self.pin['D5']
    if data & 0x04 :
      byte = byte | 1 << self.pin['D6']
    if data & 0x08 :
      byte = byte | 1 << self.pin['D7']
    self.lcd_device.write(byte)

  #  clocks  EN  to  latch  command
  def  lcd_strobe(self):
    self.lcd_device.write(self.lcd_device.read()  |  1 << self.pin['En'] )
    self.lcd_device.write(self.lcd_device.read()  &  (0xFF - (1<< self.pin['En'])) )

  #  write  a  command  to  lcd
  def  lcd_write(self,  cmd):
    self.low_level_write(En=False, Rs=False, Rw=False, data=(cmd>>4))
    self.lcd_strobe()
    self.low_level_write(En=False, Rs=False, Rw=False, data=(cmd&0x0F))
    self.lcd_strobe()
    self.low_level_write(En=False, Rs=False, Rw=False, data=0)

  #  write  a  character  to  lcd  (or  character  rom)
  def  lcd_write_char(self,  charvalue):
    self.low_level_write(En=False, Rs=True, Rw=False, data=(charvalue>>4))
    self.lcd_strobe()
    self.low_level_write(En=False, Rs=True, Rw=False, data=(charvalue&0x0F))
    self.lcd_strobe()
    self.low_level_write(En=False, Rs=False, Rw=False, data=0)

  #  put  char  function
  def  lcd_putc(self,  char):
    self.lcd_write_char(ord(char))

  #  put  string  function
  def  lcd_puts(self,  string,  line):
    if  line  ==  1:
      self.lcd_write(0x80)
    if  line  ==  2:
      self.lcd_write(0xC0)
    if  line  ==  3:
      self.lcd_write(0x94)
    if  line  ==  4:
      self.lcd_write(0xD4)

    for  char  in  string:
      self.lcd_putc(char)

  #  clear  lcd  and  set  to  home
  def  lcd_clear(self):
    self.lcd_write(0x1)
    self.lcd_write(0x2)

  #  add  custom  characters  (0  -  7)
  def  lcd_load_custon_chars(self,  fontdata):
    self.lcd_device.bus.write(0x40);
    for  char  in  fontdata:
      for  line  in  char:
        self.lcd_write_char(line)

if __name__ == '__main__':
  # define bus address, port, and pins
  display = lcd(0x27,1, Rs=0, Rw=1, En=2, Backlight=3, D4=4, D5=5, D6=6, D7=7)
  display.lcd_clear()
  # line 1 and line 2
  display.lcd_puts("I2C LCD on R-Pi",1)
  display.lcd_puts("       by gnawux",2)

Arduino 源码

主程序:

#include 
#include 
#include   // F Malpartida's NewLiquidCrystal library

#define I2C_ADDR    0x27  // Define I2C Address for controller
#define BACKLIGHT_PIN  3
#define En_pin  2
#define Rw_pin  1
#define Rs_pin  0
#define D4_pin  4
#define D5_pin  5
#define D6_pin  6
#define D7_pin  7

#define  LED_OFF  0
#define  LED_ON  1

const int SetPin = 2;
char frame[32];

LiquidCrystal_I2C  lcd(I2C_ADDR,En_pin,Rw_pin,Rs_pin,D4_pin,D5_pin,D6_pin,D7_pin,BACKLIGHT_PIN,POSITIVE);

char banner1[17];
char banner2[17];

void banner_init() {
  for(int i = 0; i<16;i++) {
    banner1[i] = ' ';
    banner2[i] = ' ';
  }
  banner1[16] = '\0';
  banner2[16] = '\0';
}

void banner_display() {
  lcd.setCursor(0,0);
  lcd.print(banner1);
  lcd.setCursor(0,1);
  lcd.print(banner2);
}

void debug(const char* str) {
  lcd.setCursor(0,0);
  lcd.print(str);
}

void initPanTower() {
  debug("begin");
  while (1) {
    if (!Serial.available()) {
      delay(100);
      debug("wating serial");
      continue;
    }
    debug("got serial");
    int len = 0;
    int nr = Serial.readBytes(frame, 32);
    while (len + nr < 32) {
      len = len + nr;
      nr = Serial.readBytes(frame + len, 32 - len);
    }
    debug("got frame");
    for (int diff = 0; diff < 32 ; diff++ ) {
      if (frame[diff] == 'B' && frame[(diff+1)%32] == 'M') {
        nr = len = 0;
        while (len + nr < diff) {
          len = len +nr;
          nr = Serial.readBytes(frame + len, diff - len);
        }
        digitalWrite(LED_BUILTIN, HIGH);
        return;
      }
    }
  }  
}

void setup()
{
  //Initialize LCD
  lcd.begin (16,2);  // initialize the lcd
  lcd.backlight();
  banner_init();

  //Enable Monitor Module
  pinMode(SetPin, OUTPUT);
  digitalWrite(SetPin, HIGH);

  //shut the led
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);

  //Start Serial Port
  Serial.begin(9600);
  initPanTower();
}

void loop()  
{
  if (Serial.available()) {
    int len = 0;
    int nr = Serial.readBytes(frame, 32);
    while (len + nr < 32) {
      len = len + nr;
      nr = Serial.readBytes(frame + len, 32 - len);
    }

    int d1 = ((int)frame[6] << 8) + frame[7];
    int d2 = ((int)frame[12] << 8) + frame[13];

    snprintf(banner1, 16, "std: %d ug/m3    ", d1);
    snprintf(banner2, 16, "atm: %d ug/m3    ", d2);
    banner_display();
  }
}

Nov 3, 2015 - Kubernetes 中的 Service Account

Comments

照例广告放最前:Hyper 作为一家新的、小的、有梦想的、不做“Copy-to-China”的创业公司,我们欢迎想创新的、有想法的、有技术的小伙伴加盟,并且我们允许远程办公。

Kubernetes 中的 Service Account 是个比较难以理解的概念,什么是 Service Account,到底是做什么的?文档中如是说——

A service account provides an identity for processes that run in a Pod. (服务帐号为 Pod 中的进程提供了一个 id)

不过,这个 Pod 中的进程的 id 是做什么用的,在用不到的时候还真让人费脑筋。管理文档里说得更详细一点,但是仍然没有提到是做什么用的。

在这种时候,一个好的例子往往胜过文档的解释。实际上,kubernetes 的官方示例里就有 Service Account 的应用。仔细看这个例子——在 Kubernetes 中运行 Cassandra

大家知道,在 Cassandra 这种全对称结构的集群里,最先启动的种子节点是最重要的,其他节点都要加入到种子节点的集群中才能保证启动的是一个集群而不会分裂成多个集群,Cassandra、Akka 集群都有这个要求。

然而,在这个例子中,是先启动一个节点,然后直接提高 Replica 数量,来做到多节点的,后面的节点是怎么找到种子节点的呢?仔细看例子的文档——

However it also adds a custom SeedProvider to Cassandra. In Cassandra, a SeedProvider bootstraps the gossip protocol that Cassandra uses to find other nodes. The KubernetesSeedProvider discovers the Kubernetes API Server using the built in Kubernetes discovery service, and then uses the Kubernetes API to find new nodes

这里提到了,image 中的 Cassandra 有一个特殊的 KubernetesSeedProvider ,由它调用 Kubernetes 的 API 来获得集群中已经存在的节点的。注意,这里就是在 Pod 中运行的进程调用 Kubernetes API 的地方,也就是 Service Account 工作的地方。

代码之前,了无秘密,看这段代码

    public List getSeeds() {
        List list = new ArrayList();
        String host = "https://kubernetes.default.cluster.local";
        String serviceName = getEnvOrDefault("CASSANDRA_SERVICE", "cassandra");
        String podNamespace = getEnvOrDefault("POD_NAMESPACE", "default");
        String path = String.format("/api/v1/namespaces/%s/endpoints/", podNamespace);
        try {
            String token = getServiceAccountToken();


这里,给出了访问的 endpoints API,并且要从本地取出 Service Account 的 Token,来获得服务发现的信息。

综上,Service Account 是 Kubernetes 用于集群内运行的程序,进行服务发现时调用 API 的帐号,帐号的 token 会直接挂载到 Pod 中,可以供程序直接使用。

Nov 2, 2015 - Hypernetes与OpenStack东京峰会见闻

Comments

广告放最前:作为一家新的、小的、有梦想的、不做“Copy-to-China”的创业公司,我们欢迎想创新的、有想法的、有技术的小伙伴加盟,并且我们允许远程办公。

10月最后一周,一年两度的 OpenStack Summit 移师东京,转瞬间朋友圈里开始有各种各样的文章出现,有些相当干的干货,比如王豪迈关于存储方面的这篇,这可是核心开发人员深入参与Design Summit,回来写的文章;当然也有一些水水的公关稿,甚至……(此处省略若干字)

这里,我也忍不住想要聊两句我们 Hyper 和 Hypernetes。

Hypernetes

我们 Hyper 这次参会,带去的新产品就是 Hypernetes,一个真正多租户的 Kubernetes Distro。Hypernetes

  • 使用我们的 Hyper 做计算隔离,轻量级虚机可以提供良好的隔离
  • 使用 OpenStack Neutron 进行网络隔离,为每个 k8s namespace 提供独立的二层网络
  • 使用 OpenStack Cinder 提供持久化的数据卷

我们在 Demo Theater 进行了演示,在我们的展台进行展示,和一些公司进行了交流,同时也在 Hacker News 上进行了发布,总的说,社区对我们的努力还是肯定的,比如,Kubernetes 项目的发起人之一、头号贡献者 Brendan Burns 就在 Twitter 远程表达了他的兴趣

quote pic

技术媒体们对此也有报道,比如 InfoWorld 这篇,不是公关稿,纯粹是社区对我们工作的反馈。

加上现场到访展台、会议上交流的公司,整个社区对我们的兴趣有点超出我的预期,感谢全体 Hyper 团队在过去两个月中的非凡努力。对我们来说,接下来就是继续努力工作,在接下来的时间里,尽快从 Demo 到可用产品。

对 OpenStack 社区与容器的看法

这次峰会上,OpenStack 社区的领头羊 Rackspace 发布了他们家的容器服务 Carina,Magnum 项目的领头人 Adrian Otto 甚至为此在 Keynote 上秀了一段娃,不过,对这个产品,大家的反馈似乎都是很奇怪、为啥还是这样…… 链接已给出,有兴趣的同学可以看看。

至于 Magnum 项目,仍然处在一种很迷茫的状态,Design Summit 上,项目陈述自己的观点是——不改变用户使用习惯、暴露原生接口给用户;不增加 OpenStack 特定的工具和新元素。同时,会上,当我问及 Adrain ,是否考虑引入通用的容器模型时,Adrain 直接表示,Magnum 将会是 k8s, swarm, marathon 的超集,你可以使用 pod,当然只能落在 k8s 上,也能使用 compose,当然这是 swarm-only 的。在这样的形势下,感觉 Magnum 似乎在沦为一个 Heat Template,不要忘了,对很多人来说,Heat 也是不成熟的。

相比之下,我倒是觉得 Kuryr 项目把 Neutron 作为 Docker 的 Network Provider,这个项目还是更有意义一些。

对 OpenStack 其他项目的一些零星感受

第一天会上,某巨头的同学透露他们在网络方面的工作更主要集中在 Open Daylight (ODL)项目上,而将 Neutron 作为一个 API 代理,这让我们看到 OpenStack 社区的参与者们开始筹划新的框架了。

而后,豪迈同学透露 Cinder 社区有独立的念头,希望摆脱对 OpenStack 的其他项目的依赖(参见开头处的链接),这真是让我们一众唯恐天下不乱的 startup 喜闻乐见啊。

其后,前面说的那位同学也提到,其实 neutron 也有摆脱对其他项目依赖的想法。

这里,如果用知乎体问我“对于网络和存储社区想要和OpenStack划清界限一事有什么看法?”,我想说

首先,这是 OpenStack 社区中,Nova 项目过于核心的结果,Nova 是一切的核心,于是网络和存储项目都和 Nova 紧密耦合,这本身就是软件工程的灾难。如今,斗转星移,容器开始唱主角的时候,当 “新星(nova)”已经不太创新的时候,是时候引入第二个计算框架了,k8s 可能无法取代 nova,或者说容器无法取代传统虚机,但容器化的架构,或者说 Immutable 的基础设施,必将占有一个相当大的份额,在此情形之下,Cinder 如此做是顺应潮流的举动,反过来看,neutron 在有了 ODL 这样的项目之后再想划清界限就有点晚了。

但是,不论 OpenStack 开源项目的演进如何,都不能阻挡基金会仍然是成功的,作为大厂商媾和的标准化组织,即使是 Nova 被劈腿了,或者 Cinder 去除唯一依赖了,基金会仍然会通过改变自己,增加新的项目,让基金会向前演进,一个项目不是 OpenStack 的全部,OpenStack 的意义在于厂商们将 IT 基础设施互操作接口标准化的决心。

最后

最后,广告再来一遍——作为一家新的、小的、有梦想的、不做“Copy-to-China”的创业公司,我们欢迎想创新的、有想法的、有技术的小伙伴加盟,并且我们允许远程办公。

Feb 24, 2015 - Blog 系统更新完毕

Comments

这个 Blog 在主机到期,迁出 wordpress 引擎之后,就放到了 github,不过,因为迁移程序的问题,文章的 url 一直是不 work 的,好几个月之后(可能有),终于修复了这些 URL。

虽然发布频率已经低到不能再低,但这个 blog 仍然活着。

Oct 18, 2014 - 一次说走就走的旅行

Comments

按,从回来开始就写这个游记,写了两个月才写出来,对不起观众了……

2014年8月10-15日,我们父子二人进行了一次草原自驾游,行程2100公里,在北京以北画了一个大圈。因为诺基亚的迅猛的裁员行动,导致团组人员和团长发生了变化,致使我们在没有仔细计划的就进行了这次说走就走的旅行。

虽然计划并不充分,但这次旅行还是很充实圆满的,这里就记录一下,供后来者参考吧。全程的照片在这里,不喜欢流水帐的同学也可以只看看照片。

##出发

8月10日早上早点之后,我们就出发了,沿京承高速一路向北,根据地图指示,需要6-7个小时的车程,不敢怠慢。

从京承高速转到承赤高速(编号依然是G45),之后又转上了承赤高速的围场支线,一路高速到了围场县,路上还是相当好走的,而且全程有指向“赛罕坝国家森林公园”的路牌,感觉不用导航也不太会走错。我们在出京前最后一个服务区太师屯停车休息加满油,出京后第一个服务区安子岭服务区吃午饭,之后又在茅荆坝服务区休息了一次。路上儿子累了睡着了,因为之前一直有坐安全座椅的习惯,加上这次有个舒服的颈枕,没有任何不适,全程都十分愉快,满分好评。

下了高速之后,跟着导航走向赛罕坝国家森林公园的入口,大概要一个多小时,这一段路不怎么宽,路况也比较一般,不过,一切才刚刚开始。

##赛罕坝:林场、草原与湖泊

赛罕坝国家森林公园是此行第一站,风景很不错,几处有水的景观都很漂亮,虽然游客不少,但拍出漂亮的照片依然不是难事,只是森林公园里的路实在是非常难走,从景区入口不远的塞罕塔开到七星湖的一段尤其让人担心车的悬挂,泰丰湖正在修路,以后可能就会好了。

赛罕坝国家森林公园的主景区是赛罕坝机械林场,当年大规模造出的人工林,如今连绵不绝,非常壮观,这是在景区的高点塞罕塔上照出的林海

因为我们之前没有详细计划,所以当我们到达景区售票处的时候,实际是一个比较茫然的状态,天色已晚不知道是住在景区里还是外面,也不知道住宿要花费多少……

不过这些问题很快解决了,我们花130块钱买了景区(赛罕坝+御道口)的门票,然后跟着一个在门口拉客的农家院的店主,开车进去了,180一晚,就这么定了。

实际上,景区里面的路还是很遥远的,店主在到店之前带我们去了塞罕塔和七星湖景区。刚才提到的塞罕塔是一座仿古建筑,是景区的制高点,可以环顾塞罕坝森林公园,乃至乌兰布统草原的景色,非常壮观。

从塞罕塔下来,经过一段漫长的搓板路,到达了七星湖,七星湖的全称好像是“七星湖假鼠妇草湿地保护区”,这里生长着大片的假鼠妇草,这种草大片生长在海拔1100米左右的高原上,但赛罕坝这里海拔大约有1500米,能生长出如此大面积的假鼠妇草非常罕见,因此格外值得保护和研究。单说风景,这里也是非常漂亮的,厚实的大片草地中,点缀着点点的湖面,宛如天上七星下凡……(有点小学生作文的感觉了),不多说了,还是上照片

到景区的第一个傍晚就收获了如此美景,真让人停不下拍照的手机,随后,我们就入住了带我们进来的农家院,住宿条件还算是不错的,不过吃的着实不便宜。

入住之后,我们抓紧时间研究了带来的攻略,幸亏下载了离线版,因为在这里基本上是无法接入网络的……经过一晚上的研究我们确定了第二天的目的地——上午是泰丰湖、滦河源头和塞北石佛等,然后下午进入内蒙古境内的红山军马场(乌兰布统草原)然后视情况决定,在乌兰布统住下还是直接前往克什克腾旗的经棚镇。

按照农家院老板的指点,我们沿着返回七星湖的路走过去,途中看到了泰丰湖的路标,不过一条主要的路正在修路,我们随即尝试了两条土路,前一条是不通的,不过后一条带我们来到了泰丰湖。

泰丰湖是一个小小的月牙形的小湖,可就是这小湖还是很漂亮的,泛舟湖上,蓝天碧水,周围的绿树以及远处的乌兰布统草原,都非常漂亮,这里不仅满足了儿子坐船的愿望,也让他骑了一回高头大马。儿子十分开心,之后,我俩就离开了泰丰湖,路过了赛罕坝林场总部的门口,然后按照店家的指点,在第二个路口左转之后,直奔内蒙古方向而去,这之后的路就变得非常好开了,基本没什么颠簸。

在到达河北和内蒙的边界之前,我们决定拐向岔路,去探访塞北石佛。这条岔路也非常好走,路上偶尔可以把车停在路边,拍拍远方草原的美景,儿子在兴奋之余还踩进了水沟……

探访石佛的计划最终还是放弃了,在开到景区的一个检查点的时候,工作人员告诉我,后面去石佛的路很难走,而且其实也没什么可看的,为了保护我们的车和今后几天的行程,我们掉头向回开。

很快,我们到达了冀-蒙边界,开出赛罕坝景区之后,我们在一个加油站又加满了油,然后开向内蒙古草原。滦河源头就在冀蒙边界上,这里有一个小市场,和一些滑索之类的游戏项目,没什么太多的东西,我们继续向内蒙古进发。

##乌兰布统:初识草原

乌兰布统草原(红山军马场)景区内,只有一小段路不好走,总体路况远强于赛罕坝森林公园,其实整个内蒙的路况都相当不错。

刚到草原我就开始感慨,自己好像看到了久违的 Windows XP 桌面,儿子也非常兴奋,到处拍照。

这里就是安北大将军佟国纲的墓,整个木兰围场,包括河北境内的围场和乌兰布统草原,都和康熙平定准噶尔叛乱的历史故事相关,这位大将军也是主角之一,似乎是康熙皇帝的舅舅还是啥。

之后,我们在午饭后还去了将军泡子,感觉草原上到处都是美景,但却有雷同的感觉,拍几十个不同的 XP 桌面也确实没什么稀罕的,但只要是个水洼就立刻让景色变得不同了,将军泡子就是这么一个景点。午饭的时候我们打听将军泡子,店家也打趣说他也不知道为什么大家都去那个小水坑。

不过我们饭后还是出发驶向将军泡子,路上路过了我们此行的第一个敖包——百草敖包,我们短暂停留之后就开到了将军泡子。

将军泡子实际离路有一两公里的距离,我们车停在大停车场上,周围有不少吃饭和卖东西的地方,我们徒步走向了将军泡子,这里实际可以搭乘一段马车的,我们没搭,有点后悔,这段路其实还挺远的。

将军泡子水确实不多,儿子在这里坐了一段羊车,也算是没白来,离开将军泡子之后,我们向回折,快到午饭的地方的时候,拐向另一个岔路,向北,向着克什克腾旗经棚镇方向开去。

在出景区之前,我又向工作人员打听了到经棚的路程,得到肯定答复后上路了。

##经棚-阿斯哈图石林:山地、草原、牛羊、岩石

对于去经棚的路,我在出发前是有疑惑的——高德地图认为这根本就没路,让我绕大圈;谷歌地图认为路是存在的,但要四个小时左右,这些让我基本不太想在第二天就直接开赴经棚。不过,很多人给了不同的答案——

第一天住宿的农家院老板告诉我,如果开到100,需要两个小时,如果开到140,需要一个半小时,嗯,没给我其他选项;不过在红山军马场午饭的时候,店家说,那边有盘山路,不是很好走,具体时间不确定;决定性的答案来自出景区时的工作人员,他们告诉我120公里,需要两小时,而且没有其他路,不会走错,于是,我和儿子一起出发了。

路上确实有一些山路,但路很平,没什么太大难度,而且路上有些地方的风景仍然不错,只是儿子睡着了,就没再停下来看,等儿子再醒过来时,已经很快就要到经棚了。

经棚类似很多小县城,G303国道穿城而过,我们没有预订酒店,根据我的一贯经验,一般情况下,在去哪或携程之类的地方看到酒店的价格,然后直接到前台问,都可以拿到同样甚至更低的价格,这次也同样没有落空。到得很早,我和儿子安顿下来,休息了一下,吃了晚饭,然后休息、准备第三天的旅程。经棚镇的酒店都有WiFi提供,我甚至还在晚上修了两个bug。

第三天我们的目标是阿斯哈图石林——

阿斯哈图石林离经棚镇大约有160公里,其实还是相当远的,路上过了热水之后,看到一大片向日葵,想起了奥林匹克森林公园的百亩葵花,这里虽然种类没那么多,不过也是很大一篇。

之后,在黄岗梁地区,我们爬升到了海拔1800米,在高山上俯瞰山谷,还是很壮观的。过了山之后,就进入草原牧区,之前看到的草原总觉的少点什么,终于在这里看到了大片的牛羊,牛在过马路的时候,还真是急不得的。

阿斯哈图石林本身是克什克腾世界地质公园的一个部分,这里冰川侵蚀的花岗岩组成形状各异的石雕,不过对我其实吸引力不大,原来石林似乎是可以开车穿过的,不过现在不行了,我们要乘坐景区的班车,在大门和三个主要景区之间摆渡,高处风大,还是应该适当多穿一件衣服的。不过倒也不必租棉衣,没那么夸张。

因为现在阿斯哈图石林不能穿过去了,也就没法从这边开向达里诺尔了,于是,我们从阿斯哈图石林出来,原路回到经棚镇,吃饭休息,准备第四天去达里湖,并直接开向锡林浩特,因为不确定到达的时间,提前在去哪订了一间酒店,这也是我们这一行唯一一次提前预订酒店,事实证明并不成功,呵呵。

##达里湖:草原大湖(以及锡林九曲、贝子庙)

第四天一早,我们出发,沿G303国道一路向西,途中新的高速正在边上修,下次再来可能更好走了,不过老国道也很平,只是没有更多车道而已。

我们的第一站是达里湖,这是一个真正的草原大湖,即使是在大比例尺的地图上,也能清晰地看到它,我们首先开到的是一个小博物馆,这里介绍了同样作为克什克腾世界地址公园一部分的达里湖的一些情况,我们在这里没做太多停留,转到了真正的达里湖畔。

我们在达里湖公园门外停下车,乘坐园区的小车,沿着湖边,一直到达里面码头的位置,毫无疑问,我们选择了坐船去湖中央转一圈,湖真很大,烟波浩渺,转了一大圈回来,我们又继续乘坐小车到达观鸟长廊,用这里提供的望远镜看到很多小鸟啥的。

从达里湖出来,时间其实还挺早,我们决定继续驶向锡林郭勒,去找大草原。

按照计划,在到达锡林浩特之前会先到白音锡勒草原,这里是锡林浩特草原的精华部分之一,有跑马场等地方可以一去。实际情况让我们比较失望,跑马场是跑赛马的,不适合儿子这么小的小朋友,我们又试着找了一下这附近的几个景点,都无功而返,于是我们直接开向锡林浩特了。

在锡林浩特,我们找到入住的酒店之后发现时间尚早,于是就出发寻找传说中的锡林九曲去了。这个方向实际也就是G207国道——我们回家的方向,当我们找到锡林九曲的时候略有失望,这里实际上是一个餐饮中心——话说可能锡林浩特所有的景点都是餐饮中心,但是景点之外的草原也随处是风景。

锡林九曲餐饮中心旁边,我们看到了一小片湖水,据说应该是锡林河和水库,不过和我们刚刚路过的达里湖当然完全不能比了,这里有一个敖包,湖对面还有一些牛羊,拍出照片来还是挺好看的,不过和达里湖真的没法比。

从锡林九曲出来,我们一路回到锡林浩特的酒店,这里离贝子庙已很近,不过有点晚,贝子庙已经关门了,我们爬到上面看了一串大敖包,拍了照片,在广场上玩了一会儿,然后回到酒店休息。

##乌珠穆沁、蒙古汗城:深入草原

第五天,我们的目的是——看锡林郭勒大草原,晚上入住蒙古包。我们选择的路线是从锡林浩特出发,沿省道前往西乌珠穆沁旗和蒙古汗城,然后原路回到锡林浩特,并在合适的位置找一个蒙古包入住,完成内蒙古的完整体验。

我们当天出发发现当地人很少买早点啊,在锡林浩特转了一圈才在肯德基吃了本次旅行最舒服的一次早餐,之后就上路大草原了。我们先在锡林浩特附近,按照酒店老板的指引到了毛登的一个有动物啥的的园子,不过我和儿子都对这里面没啥兴趣,感觉还是个卖餐饮娱乐服务为主的比较坑的地方。

我们回到路上继续向蒙古汗城进发,一路上草原很漂亮,天空阴沉沉的,云很低,时不时还下场雨,路上看到有车停下来拍天上,我们也停下一看,还真看到了天空正上方的彩虹!

长途跋涉,绕过西乌珠穆沁旗的县城之后,我们到达了蒙古汗城,至此我们得到了一个惊人的发现——锡林浩特所有的旅游景点其实都是餐饮娱乐中心,蒙古汗城也是。虽然如此,我们还是在这里骑到了骆驼,儿子很开心,只是没有适合我们两个人吃的东西。

##蒙古包、返程:告别草原

在蒙古汗城转了一圈之后,我们向回回到县城吃了饭——事实证明儿子在这里吃的东西足够让我们后悔……

我们在开到锡林浩特之前加满了油,这些油会在第六天带我们回家。天下起了雨,我们沿着G207国道向南开,虽然不知道哪里有蒙古包,但我们坚信可以找到。

很快我们就找到了方法,当在路边看到牧民的家的牌子的时候,我们就在路边停下车,按上面的电话打过去,问有没有蒙古包住。这个方法很灵验,第二个电话就找到住的地方了。当天不是周末,蒙古包都很少有游客住,第一家牧民都没在蒙古包,第二家也只有我们一家人。因为这些牧民都是随机找的,我对安全性啥的心里也没有谱,在到达之后就把我们的座标发给我老婆了,呵呵。

这家牧民有500头羊,我们看见的都是些小羊以及带小羊的母羊,非常意外的是,羊们很好奇,不仅主动靠近儿子,而且还围住我们的车。

当天的晚饭,我点了内蒙特色手把肉,不过,儿子因为疲劳+中午吃的饭消化不是很好+在牧民的草场里闻到不少牛粪味儿,这三者的作用,在手把肉入口之前就吐了出来,手把肉基本上原封不动的带回了北京。

虽然蒙古包很新奇,不过儿子不舒服,还有一点发烧,我给他冷敷了一宿,第二天(第六天)一早就上路开始返程。

返程路程十分顺利,走S27锡张-SXX张石-G7京新-G6京藏,全程高速,其中锡张高速最后一段是8月13日才通车的,我们是10日出发,15日返程,刚好赶上通车,非常幸运。路上还是有很多漂亮的草原和一些景点,不过,我和儿子已经对草原审美疲劳了,都没有心情停车欣赏了。

因为担心儿子不舒服,路上只在刚进入河北的察北服务区上了一趟厕所,连开了七个多小时直接到家,不过儿子好像只是疲劳,在路上睡了两觉之后就全好了,算是万幸。

##后记

流水帐终于完成了,儿子因为查出来对某些杂草有六级的过敏,估计以后都不会去草原玩了,这次草原看到吐也算是值了吧。一点想法是——

还是有点准备更好,但准备也不需要过多,说走就走,随走随玩也挺好;

围场挺好的,除了路不好走,围场比大草原更有味道;

大草原也挺好看的,走到哪觉得好看就下来看看,景点其实无所谓了

路越来越好走,如果走张家口这个方向到锡林浩特,其实已经是全程高速了

就这些,供大家参考,祝大家玩得开心。

Oct 18, 2014 - 第一次对汽车进行DIY

Comments

多年以来,看了这么多有意思的行车记录仪拍的视频,以及这么多的碰磁事件,一直对记录仪很心痒,琢磨着买一个,这次调研之后,郑重动手了。

促使我动手的一个重要因素是,我发现了一件好玩事情——从保险盒取电,而不是用点烟器供电,走暗线到记录仪。这样的好处在于——点烟器还是很常用的,空出来可以给导航和其他东西用,而且暗线也避免了视觉上的干扰。

取电的要点是用一个专用的取电器,相当于从一个保险的正极取出12V直流电,过一个新加的保险丝,过调压器调到5V的USB供电,输出到记录仪,回来的负极接到靠近保险盒位置的一个金属点上,嗯,学电子一般都知道,直流设备里,接地和接机壳一般是同一件事情,所以这个没什么问题,这里是不能并回到取电的保险丝负极的,否则那个保险丝的意义就没有了。取电部分的效果如图

这里的主要技术难度是中间的接头部分上烙铁了,用焊锡接了一段线。之后就是打开A柱的护板,把线从里面走上去,沿玻璃顶部走线,线要塞进内饰里,不过因为有降压电路的小盒,没有完全塞进去,不过也没什么妨碍,连接行车记录仪部分的效果如下图。这里还多留了一个USB母口,觉得万一可能有什么用,其实实际上没什么用……

就这样,还是很简单的。取电器、保险丝和降压线都可以在万能的淘宝买到,这里就不做广告了。

Jul 28, 2014 - 搬家

Comments

转自微博:IT土豪刘老师问我,你有没有一种感觉,就是其他人活的比你轻松幸福一些。我说嗯,常有,这一定是错觉对吧。他说,哦,那倒不是,应该都是真的。

我也时常这么感慨啊,可是感慨归感慨,自己还是要过自己的生活。

这两天在打包,准备搬家,为了陪伴孩子上小学,搬去小学附近租房,这一去不知何时还能搬回到这里、会不会再搬回到这里,但可以确定的一点是,即使能回来,到那一天,这里也必然不似今天这般模样了。

这个家承载着我们不多的6年多的时光,但它又那么难以释怀,它陪伴斯屹从十月怀胎一直到准小学生,又陪着斯诺走过人生最初半年;这个家陪伴我们跨过30岁,从新婚夫妻到为人父母,从初涉职场到为人前辈,人生中,可以堪称黄金年华的6年又能有几个呢。

还记得,刚刚搬到这里的时候,我们给还没出生的斯屹买了一张童床,我拍了给小床组名叫“虚位以待”的照片,向周围的人宣告了斯屹即将到来,小床又重新装饰漂亮,传给了斯诺,伴她度过了最初的几个月。我们不会再带走这张小床了,将来斯诺能否记得这张小床和这个家呢。

还记得,5年前的结婚纪念日,就在这个家里,我悄悄为爱人送上了99朵玫瑰,因为是清早敲开鲜花市场的门买到的新鲜玫瑰,回来像择菜一般,一点点修剪满是刺的99朵玫瑰,之后两个人傻傻地、开心地和一桶红玫瑰合影。玫瑰早已谢了,我们也将要离开这里了,芬芳却不会离我们而去。

还记得,3年多前,斯屹3岁的时候也从奶奶家搬回这里,和我们重新住在一起,开始上幼儿园,为了能和爸爸妈妈在一起,他从一开始就很坚强地忍住不哭,一直成长到后来的飞一样地骑着红色自行车,在楼后运动场上飞驰的少年,这里还有他幼儿园的小伙伴们,斯屹一定不会忘记这里的,这里有他最无忧无虑的童年。

当然也还记得去年圣诞节前,刚刚出生的宝贝诺诺从医院回到家,我和斯屹妆点上最最漂亮的圣诞树,温馨幸福的一家三口变成了温馨幸福的一家四口,诺诺虽然还不会说话,但七个月来,诺诺与这个家不曾分开,诺诺的小车也跑遍了楼前楼后,这次我们当然也会带着诺诺一起走,小诺诺将来一定记不起这个家了,但爸爸妈妈和哥哥的爱,会如这里的温馨一直陪着她长大,或许将来有一天她还会回到这里。

离开真是件让人忧伤的事情,总有点不知前路在何方的感觉,可我们又不能停下,只希望我们一家继续相亲相爱,希望我们的生活也能越来越舒适幸福,希望孩子们健康快乐。

上面提到的几张照片的链接:https://www.flickr.com/photos/gnawux/sets/72157645584252908/ 相对于他爷爷奶奶的“天通苑家”,儿子总把这里叫“蓟门桥家”,相册就也叫这个名字了。

后记,上次丢了自行车,写了篇博客,后来读者纪阳老师居然送了我一辆自行车,自行车如今仍在,昨天我打包的时候竟然还找到了买车的凭证,在此谢谢纪阳老师啊;这次搬家,半夜coding了一阵之后乎有所感,又写了一篇,就不期待有人送房子了,还是自己努力工作吧,哈哈。

Jun 22, 2014 - 推荐两款值得买的 Android 应用

Comments

BetterBatteryStats 和 XPrivacy,一个对电池,一个对隐私,都是瞄着别的应用的应用,总体感觉比较值得买。其实我买过很多移动应用 iOS 和 Android 平台都有,算起来可能有两千块钱了,总的讲,Android 平台真正值得买的好用的应用并不是很多,而且有不少买了之后就不再装了…… 不过这两款我还是要推荐一下。

BetterBatteryStats

Play Store 链接:https://play.google.com/store/apps/details?id=com.asksven.betterbatterystats ,这个应用可以统计手机睡眠相关的情况,帮你发现省点的方法。

首先说我的一个观点——手机是用来拿在手里玩的,不是用来省电的,如果你什么应用都不用,什么功能都不开,屏幕亮度还调到最低,那么,为啥花四五千块钱买个手机呢,去买个我老婆他们公司(最近改叫微软移动的那家)生产的两百块钱的手机好了。不过,手机虽然是用来玩的,可也只是有电的时候才能玩,所以省电也是需要的,BBS这款应用,可以帮你确定什么应用阻碍了手机进入睡眠,确定是ROM的问题还是应用的问题,进一步解决。

比如说这个——

可以看到,一半的 PartialWakeLock 都来自淘宝无线,相比之下,微信就很少影响到手机的睡眠了,而且我得说,我在这段时间里,动不动就看看微信,可是从来没打开过淘宝,所以,这个结果就是——我卸掉了淘宝,之前还卸掉了奇艺。

当然,这些问题只是当时有,不一定现在的版本还有,本着授之以渔的精神,我把应用介绍给大家,大家自己看吧。

XPrivacy Pro

XPrivacy 是 Xposed 框架下的应用,不在 play store 里买的,网站在这里 http://www.xprivacy.eu/ ,这个程序是开源的,可以在 GitHub 上得到源代码,付费也就是捐款,捐款得到 license 放到主存里就可以了。与之类似,CM 的 PrivacyGuard 也是用来限制应用权限的,不过 XPrivacy Pro 感觉功能更丰富一些,包括可以导出导入、可以看详细的权限使用记录,有模板,支持各个API的管理,非常丰富。

隐私这东西,看不见摸不着,而且保护隐私和便利性常常是冲突的,比如,有好几个应用可以拦截短信读取验证码,省去了看-背-输 这个过程,这本来是好好的,但我偏偏不喜欢。因为我不想授权给应用来读我的短信(虽然我的短信差不多全是机器自动发来的)。当然,这个见仁见智了,说得通,只是看你信不信、乐意不乐意了,但我们希望把决定权握在自己手里,而不只是装或不装。

Android 相比于 iOS 系统对每个应用的每个权限的授权功能是非常不足的,XPrivacy 就在这方面可以为用户提供更好的保护。我的用法是——对于要用的应用,开放必须开放的和我认为可以开放的权限,对于可用可不用的应用,如果有莫名其妙的权限要求,那就卸掉。这里也有个例子,就是今天在微博上吵的——

支付宝(和其他阿里的无线应用,诸如淘宝、虾米)就这样,即使你没打开它,它也在后台读你的剪贴板。其实读设备ID的请求,差不多所有应用都有,这个用于应用统计也不违反政策,如果咱不喜欢,关掉就是了,可是读剪贴板是为什么呢?Pocket 会在打开的时候读,刚好可以帮你收藏拷进来的链接,这个可以理解,但在后台运行的支付宝和虾米服务就比较难理解了,完全不知所云。

额外的废话

作为混迹技术圈这么多年的人,我和阿里很多人都是关系远近不同的朋友,其中很多都是比我强很多的大牛,我和阿里,特别是淘宝、支付宝没有任何恩怨,我贴他们是因为我用着他们,没装的应用谁会注意到呢。

有人说他们可能是在做好事,在帮助我或其他小白,嗯,可能是,我没看代码不知道你们在做什么,但一者,建议你们公开解释下这是在干什么,最好在 play store 的应用介绍里直接说出来为什么用这个权限,很多应用都这么做了;二者,希望你们给用户选择的权利,不是所有用户都喜欢你们这样的行为的,毕竟我们知道国内偷取用户隐私的下三滥应用很多,希望你们作为大公司出来的应用,能多点节操。

有人说爱用不用,XXX还也读剪贴板呢,对,就是这个道理,在便利和其他方面权衡是我自己做的,但我觉得你们这方面做得不好,拿出来吐槽有什么不行呢,反正在我用的应用里,在后台乱读剪贴板的就你阿里一家。

最后,我想很贱地问一句,你们阿里无线996工作了这么长时间,Android应用就做成这样(包括电池那段和剪贴板这段),你们觉得你们对得起你们加班的时间么?