Category Archives: Tech

资源占用率监测方法

CPU

TOP,地球人都知道
[root@~]# top
top – 14:50:56 up 22:31, 7 users, load average: 45.76, 33.64, 15.93
Tasks: 622 total, 1 running, 621 sleeping, 0 stopped, 0 zombie
%Cpu(s): 3.6 us, 0.2 sy, 0.0 ni, 93.9 id, 2.4 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 13198510+total, 56110604 free, 41665376 used, 34209120 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 89487664 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
2566 root 20 0 629720 561460 70156 D 67.2 0.4 0:19.35 clang
3197 root 20 0 144644 69224 42192 D 15.6 0.1 0:00.66 clang
3057 root 20 0 308968 234124 53692 D 12.6 0.2 0:06.64 clang

磁盘IO

[root@~]# iostat -x

Linux 3.10.0-514.el7.x86_64 (bsabj36) 02/10/2020 _x86_64_ (32 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle 11.12 0.00 2.45 0.70 0.00 85.73
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %utilsda 0.01 0.22 8.82 7.20 1072.97 386.24 182.17 0.17 10.34 2.75 19.65 9.98 15.99sdb 0.02 0.57 9.43 8.03 1064.23 325.95 159.18 0.20 11.41 22.09 29.59 11.08 19.35sdc 0.00 0.17 9.58 1.88 955.05 222.93 205.64 0.01 0.51 12.96 68.48 8.94 10.24sdd 0.05 0.56 1.97 3.99 42.70 73.31 38.93 0.14 22.93 5.20 31.71 3.53 2.10dm-0 0.00 0.00 1.34 3.35 30.00 29.12 25.22 0.11 22.51 5.16 29.43 3.13 1.47dm-1 0.00 0.00 0.06 0.16 0.23 0.64 8.00 0.11 484.35 7.16 652.83 0.19 0.00dm-2 0.00 0.00 0.64 0.77 12.43 43.54 79.25 0.05 36.75 5.56 62.72 5.55 0.78
看来rkB/s wkB/s比较高,可能存在io竞争看看哪些进程io占用率高?iotop -oPTotal DISK READ : 3.37 M/s | Total DISK WRITE : 1689.08 K/sActual DISK READ: 3.37 M/s | Actual DISK WRITE: 15.33 M/s PID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND 991980 be/4 qemu 3.37 M/s 0.00 B/s 0.00 % 9.29 % qemu-kvm -name WANNA_defend_win7_update_BJOBM1 -S -machine~alloon-pci,id=balloon0,bus=pci.0,addr=0x7 -msg timestamp=on 845146 be/4 qemu 0.00 B/s 1520.52 K/s 0.00 % 3.57 % qemu-kvm -name WANNA_defend_win7_update_GDSZ1 -S -machine ~alloon-pci,id=balloon0,bus=pci.0,addr=0x7 -msg timestamp=on 232279 be/4 qemu 0.00 B/s 158.02 K/s 0.00 % 0.73 % qemu-kvm -name MNG_ESPC -S -machine pc-i440fx-rhel7.0.0,ac~alloon-pci,id=balloon0,bus=pci.0,addr=0x8 -msg timestamp=on 860489 be/4 qemu 0.00 B/s 0.00 B/s 0.00 % 0.01 % qemu-kvm -name WANNA_SECURITY_RSAS_off -S -machine pc-i440~alloon-pci,id=balloon0,bus=pci.0,addr=0x7 -msg timestamp=on 993858 be/4 bsaworke 0.00 B/s 10.53 K/s 0.00 % 0.00 % java -Dproc_nodemanager -Xmx1000m -Dhadoop.log.dir=/home/b~rties org.apache.hadoop.yarn.server.nodemanager.NodeManager

网络IO

[root@~]# iftop -n -i eth0
-n表示不解析域名,-i表示对应网络接口

                            191Mb                                    381Mb                                    572Mb                                    763Mb                               954Mb
└───────────────────────────────────────┴────────────────────────────────────────┴────────────────────────────────────────┴────────────────────────────────────────┴────────────────────────────────────────
192.168.19.14 => 101.6.8.193 42.5Kb 34.5Kb 34.5Kb
<= 2.75Mb 2.45Mb 2.45Mb 192.168.19.14 => 10.63.1.90

RSAC 2020 | 云安全观察

RSAC 2020如期在旧金山举办,还是2月,还是在Moscone Center,只是没有了诸多中国同行的参加,也没有了百度游艇。很可惜我没有亲身参加本届RSAC,(也不知道市场街梅西百货外面的市政有没有弄好,这也似乎成为一个旧金山新景点了,囧),因而文中不会出现身临其境的感受,所以更多的谈的是从会议session中观看和阅读的感觉。

时间有限,还没来得及看所有的胶片,不过从云安全高峰论坛(CSA Summit)、云安全和虚拟化(Cloud Security & Virtualization)和DevSecOps 和应用安全(DevSecOps & Application Security)几个Track的内容中,我有以下几个观点,仅供探讨。

观点1 :云计算已经成为了互联一切的基础设施,云安全已经成为了纯安全问题

    第一天早上CSA高峰论坛[1]、第一天下午创新沙盒,第二天上午Keynote乎已经成为RSA的近几年标配,也是三大看点。CSA高峰论坛作为先发阵容中的先发队员,其影响力可见一斑。不过很有意思的是,每年CSA的影响力是越来越大,各种标准、工作组和培训,应该都让它挣得钵满盆满,覆盖范围也从最初的云计算安全扩展到物联网安全、软件定义边界等交叉领域,可见,云计算已经成为互联一切的基础设施,云安全联盟的触角已然不满足狭义的公有云和私有云计算安全领域了。

可以说,CSA和安全厂商和已经成为了合作伙伴的关系,在共同的研究点和产品方向互为背书,互为推广。例如,2019年的高峰论坛上几家做SDWAN安全的厂商在大谈软件定义边界,也为它在2013年提出的SDP捧场。今年OneTrust的VP Kevin Kiley在讲供应链安全,其中就是对供应链中的第三方厂商的安全进行评估,也就是Gartner近两年提的IRM(Integrated Risk Management)中的VRM(Vendor Risk Management),为什么OneTrust会在云安全的会场谈这个话题呢?因为云计算一个很大的挑战是用户对云服务商的信任,CSA在前几年提出了Consensus Assessments Initiative (CAI)[2],即对用户让云服务商就Cloud Controls Matrix (CCM)标准填写评估,从而得到第三方云基础设施的可靠度。显然OneTrust的方案也是契合该方向的。可以说云安全联盟在商业运作上非常成功,通过与厂商的合作,与客户的培训,形成了云安全领域很好的生态体系,共同推进云计算安全的发展。

另一方面,云计算既然成为了普适的基础设施,提供了计算、存储、网络、函数等服务,那么客户就会将云计算作为一种内生资源,嵌入在他的基础设施中,最终形成统一的IT架构,近两年多云(Multi-Cloud)、混合云(Hybrid Cloud)、SDWAN就比较热,在这样的IT环境中提供安全产品、安全服务,那前几年的云安全产品或方案必然要融入传统环境,提供统一的功能。可以预见,在未来几年,安全厂商的安全方案不会显示地带有“云安全”的定语,因为这就是默认选项,即云安全已经成为纯安全问题。一个证据是今年CSA高峰论坛的话题则覆盖了网络检测响应(NDR)、供应链安全、数据泄露响应、CISO视角等各安全细分领域的话题,但上午的session标题中都没有出现cloud一词。当然细看内容,其实云安全的理念已经融入其中,甚至可以说,大家无论谈安全理念,或是安全技术、安全方案,都是面向云计算环境。例如Extrahop Networks的COO Raja Mukerji在谈检测响应,主张将NDR、EDR、SIEM组合,构建面向公有云的检测响应机制,实现云原生的安全。

当然,虽然传统安全问题会发生在云计算场景中,但云环境也有其独特的地方,所以在云安全和虚拟化(Cloud Security & Virtualization)session中,如子域名接管[3]的原因在于一些子域名是租用的,管理不当容易被恶意租户发现并接管;又如好几个胶片谈到暴露面检查,就是Gartner说的CSPM(Cloud Security Posture Management),本质来看就是传统的服务(端口)暴露和弱口令(正如现在很多互联网上脆弱的物联网设备一样),转变成了公有云上的存储资源暴露和弱口令。所以这些本质来说是传统安全问题,但公有云计算环境下有新的特点,值的我们重视。

观点2: 云安全更加实战,已从纯合规性要求转向攻防要求

从前几年AWS在各个大会上有独立session做AWS安全入门培训,彼时大部分的客户对公有云还不太熟悉,一部分是因为公有云服务太多、配置太复杂了,另一部分原因是,对于攻击者而言也比较新,相对而言,IaaS和PaaS的云安全还是以合规性要求居多。

而如今,云计算对于攻击者而言,开放API、灵活的资源编排,俨然成为了另一个维度好用的攻击资源;另外,云用户错误配置,也给觊觎云上敏感数据的攻击者可乘之机。所以无论是数据面安全CWPP(Cloud Workload Protection Platform),还是管理面安全CSPM,各类云安全厂商也兴起,从本届RSAC云安全session中的内容来看,跟往届相比更加实战。

例如前面提到的子域名接管攻击,在两个胶片[3,4]中提到,其中[3]是星巴克安全团队作为受害者出来讲,更令人信服。而[4]则更全,介绍了十种面向云平台的攻击链,其中侦查阶段大部分是CSPM关注的暴露token、bucket等,另一部分则是恶意内部攻击者,即前面提的供应链覆盖的内容。

当然只有用户和安全厂商显然不全,session中还有一个是来自AWS的Ben Potter,职位是the security lead for Well-Architected,属于架构师,介绍了AWS的架构(Well-Architected)中的安全设计,可以看出,AWS的安全体系已经覆盖了事前管理、准备,事中检测和响应,事后恢复的闭环,其中还提到使用了金丝雀账户,部署了一些诱饵,这说明云厂商的安全团队已经不止关注传统的清理(hygiene)和被动防护的工作,也开始做一些主动防御的工作了。

总体而言,检测响应已经从传统企业环境转向云计算环境,如[5,6]都介绍了云服务商和用户如何做检测响应的经验,[6]则是介绍了在线交易的领域如何实现身份和访问控制(SSO、MFA,RBAC),数据安全(加密、密钥管理),应用安全(API安全,session管理),日志和监控(分析,日志集中),事件响应(告警、IR剧本),在云计算环境中必须要引入自动化和xDR才能满足规模化的要求。

作为防守方,一家小公司IMG Security的咨询师,[9]介绍了云环境下的威胁狩猎,案例非常具体,涉及到渗透测试和事件响应

观点3:云原生进入主流,从只谈容器安全、Kubernetes安全到云原生安全,代码、应用

在2019年的RSAC的早期厂商展览,已经有一两家做Kubernetes和容器安全了,而今年的云安全和DevSecOps session中,有两篇Kubernetes攻防的胶片,还有一篇介绍云原生安全和Serverless安全,说明这话题已经被主流观众所关注,而且关注点不断向上,从容器到编排,再到无服务和云原生安全。

例如,SANS的培训师在[7]hack了AWS的lamda函数,通过反向代理获得无服务的操作系统细节,见下图。

图片包含 屏幕截图

描述已自动生成

然后分析了如何从外部获取凭证进入容器,进而收集容器内部更多凭证,横向移动,建议将函数放在vpc里面,减少暴露面。

在[8]中,讲者是一家安全资讯公司inguardians的CTO,先介绍了Kubernetes下的攻击思路,在master节点攻击api server等几个核心组件,在slave攻击kubelet和运行时容器,给了一个自己开源Kubernetes的渗透测试工具(https://github.com/inguardians/peirates),该工具主页demo介绍了获得暴露的secret,然后从api-server创建账号,新建容器,并得到反连shell。另外给了一个测试训练环境,供学习 https://www.bustakube.com/

从以上几个胶片的内容可以看出,今年云计算安全很明显的,安全倾向于攻防细节,环境则侧重于编排系统之上的部分。

观点4:DevSecOps成为热点,DevSecOps和云原生安全不断融合,创造了一些共同的话题

敏捷开发DevOps似乎与云计算是两个维度,但“容器-编排”可以支撑敏捷CI/CD的开发模式,而“编排-无服务”的云原生运营模式可以支撑大规模弹性的应用场景,这套技术栈似乎为全世界绝大多数的开发者所青睐,而容器-Kubernetes-Serverless又是云原生的底层技术,所以随着敏捷开发本身的安全机制(DevSecOps)不断发展,两种安全视角的融合不断加深。今年的云安全session中有一些问题的起源,如代码中的硬编码token、代码仓库的暴露凭证,都是安全敏捷开发需要关心的内容,而今年的DevSecOps 和应用安全(DevSecOps & Application Security)session中,则有一篇[10]是专门介绍Kubernetes的访问控制安全的。

观点5:人是对抗永远的主题,人的因素不可忽视

今年大会的主体是Human Element,不可避免地很多胶片中也出现了这个元素,大部分都是简单涉及,不过确实有两篇是专门对人的因素进行探讨。

如[11]介绍了在传统环境和云环境中不同的思路,在云环境中做安全应该与云计算的思路匹配,如安全即代码,拥抱自动化,此外要避免人为错误所造成的影响,组织红蓝对抗。而[12]是出现在敏捷开发的session中的,很有意思的讨论了工作时间、团队规模和修改他人代码频繁度等因素对代码安全性产生的影响。总之,技术、流程和人是信息安全的三个组成部分,人的因素在不断的提升。如何发挥人的主观能动性,对于提升安全防护效率至关重要。

总之,云计算已经成为了连接万物的普适的基础设施,云计算安全已经进入了下半场,如何形成统一的安全体系,如何提升云计算的真实安全水平,如何提升使用云计算的各种团队的安全能力,将是接下来云安全的发展方向。

参考链接

[1]  https://www.rsaconference.com/usa/agenda/csa-summit-privacy-and-security-in-the-cloud

[2]  https://cloudsecurityalliance.org/research/working-groups/consensus-assessments/

[3] Same Thing We Do Every Few Minutes, Pinky – Try to Take Over All Your Subdomains ,RSAC 2020

[4] Break the Top 10 Cloud Attack Killchains, RSAC 2020

[5] Using Automation for Proactive Cloud Incident Response,RSAC 2020

[6] Untangling SaaS Security in the Enterprise, RSAC 2020

[7] Defending Serverless Infrastructure in the Cloud, RSAC 2020

 [8] Kubernetes Practical Attack and Defense, RSAC 2020

[9] Cloud Threat Hunting, RSAC 2020

[10] Compromising Kubernetes Cluster by Exploiting RBAC Permissions, RSAC 2020

[11] Hacking Your Security Culture for the Cloud,RSAC 2020

[12] Which Developers and Teams Are More Likely to Write Vulnerable Software,RSAC 2020

Kubernetes离线安装

为什么要有这个项目

Kubernetes的软件源和镜像是托管在google的服务的,所以你懂的。这个项目是将google侧的东东缓存到本地,加速安装过程。

项目的地址为 https://github.com/marvelliu/kubernetes-offline-installer

安装说明

目前支持Ubuntu 18.04(其他版本需要自己下载了),因为是离线的,所以版本部署最新,可能对已有的软件要降级,可安装完再apt-get upgrade即可

确定安装kubernetes的版本

目前支持Ubuntu 18.04,默认安装的kubernetes版本为install_master/slave.sh的KUBE_VER,如果需要安装其他版本则修改$KUBE_VER,目前支持的版本见debs/images下的目录。

安装

主节点运行install_master.sh,从节点运行install_slave.sh

当主节点运行完后,会出现如下提示:

kubeadm join 192.168.19.244:6443 –token 3103e3.33cc37005f04433c \ –discovery-token-ca-cert-hash sha256:7697a85c0199533f9c891f9f2e2f92931b02d5a60ef25dfc0ad52c25f7f0f0eb

则将该命令在所有从节点运行即可。

注意事项

运行过程中会连接dl.k8s.io,事先已重定向到了127.0.0.1,出现warning不需要管

Linux hook函数简单实践

#起因
最近对hook函数比较感兴趣,希望能捕获所有某种系统调用,看网上有一篇文章(http://opensourceforu.com/2011/08/lets-hook-a-library-function/) 介绍hook malloc函数,但可能内核实现不同,4.4.0上有问题。特写一篇文章进行更新。

原文中,hook函数中调用printf,其实printf、fprintf等函数都会再调用malloc函数,导致segment fault。正确的方式应该是用snprintf写入内存,然后调用write函数输出。同理,如果在hook open函数的时候,调用了open函数,也会造成segment fault的问题。

#示例
a.c:

#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
int main(void)
{
int p;
printf("calling from main...\n");
p=(int *)malloc(10);
if(!p)
{
printf("Got allocation error...\n");
exit(1);
}
printf("returning to main...\n");
free(p); /
freeing memory from heap */
printf("freeing memory...\n");
return 0;
}

prog2.c

#define _GNU_SOURCE
#include <stdio.h>
#include <stdint.h>
#include <stdarg.h>
#include <string.h>
#include <dlfcn.h> /* header required for dlsym() */
#include <unistd.h>

/* lcheck() is for memory leak check; its code is not shown
here /
void lcheck(void);
void print(const char
format, ...);

void* malloc(size_t size)
{
static void* (my_malloc)(size_t) = NULL;
print("inside shared object...\n");
if (!my_malloc)
my_malloc = dlsym(RTLD_NEXT, "malloc"); /
returns the object reference for malloc /
void *p = my_malloc(size); /
call malloc() using function pointer my_malloc /
print("malloc(%lu) = %p\n", size, p);
lcheck(); /
calling do_your_stuff function */
print("returning from shared object...\n");

return p;

}

void free(void* p){
static void (my_free)(void ) = NULL;
if (!my_free)
my_free = dlsym(RTLD_NEXT, "free"); /* returns the object reference for free /
my_free(p); /
call malloc() using function pointer my_free */
print("free %p\n", p);
return;

}

int open(const char pathname, int flags, mode_t mode){
static int (
my_open)(const char *, int , mode_t ) = NULL;
if (!my_open)
my_open = dlsym(RTLD_NEXT, "open"); /* returns the object reference for open /
int res = my_open(pathname, flags, mode); /
call malloc() using function pointer my_open */
print("open %s, flags: %d, mode: %u\n", pathname, flags, mode);
return res;
}

ssize_t read(int fd, void buf, size_t count){
static int (
my_read)(int ,void*, size_t) = NULL;
if (!my_read)
my_read = dlsym(RTLD_NEXT, "read"); /* returns the object reference for open /
ssize_t size= my_read(fd, buf, count); /
call malloc() using function pointer my_open */
print("read fd %d, buf: %s, count: %u, read size: %u\n", fd, buf, count, size);
return size;
}

ssize_t recv(int sockfd, void buf, size_t len, int flags){
static int (
my_recv)(int ,void*, size_t, int) = NULL;
if (!my_recv)
my_recv = dlsym(RTLD_NEXT, "recv"); /* returns the object reference for open /
ssize_t size= my_recv(sockfd, buf, len, flags); /
call malloc() using function pointer my_open */
print("recv fd: %d, buf: %s, len: %u, flags: %d, read size: %u\n", sockfd, buf, flags, size);
return size;
}

int accept(int sockfd, struct sockaddr addr, socklen_t *addrlen){
static int (
my_accept)(int ,struct sockaddr*, socklen_t) = NULL;
if (!my_accept)
my_accept = dlsym(RTLD_NEXT, "accept"); /
returns the object reference for open /
ssize_t size= my_accept(sockfd, addr, addrlen); /
call malloc() using function pointer my_open */
print("accept fd: %d, addr: %u, len: %u, fd: %u\n", sockfd, addr, addrlen, size);
return size;
}

ssize_t write(int fd, const void buf, size_t count){
static int (
my_write)(int ,const void*, size_t) = NULL;
if (!my_write)
my_write = dlsym(RTLD_NEXT, "write");
ssize_t size= my_write(fd, buf, count);
print("write %d, buf: %s, count: %u, write size: %u\n", fd, buf, count, size);
return size;
}

void lcheck(void)
{
print("displaying memory leaks...\n");
/* do required stuff here */
}

void print(const char* format, ...){
static char buf[1024*2];
char tmp[1024];

static int logfd = -1;
if (!my_open)
    my_open = dlsym(RTLD_NEXT, "open");

if (!my_write)
    my_write = dlsym(RTLD_NEXT, "write");

if (!my_close)
    my_close = dlsym(RTLD_NEXT, "close");

if(logfd <0 || lseek(logfd, 0, SEEK_CUR)<0){
    logfd = my_open(logfile, O_RDWR | O_CREAT| O_APPEND, 600);
}


va_list argptr;
va_start(argptr, format);
vsprintf(buf, format, argptr);
va_end(argptr);
//int res = my_write(1, buf, strlen(buf));
int res = my_write(logfd, buf, strlen(buf));
if(res <0){
    perror("write error");
}

}

然后编译,运行:


gcc prog2.c -o libprog2.so -ldl -fPIC -shared
LD_PRELOAD=/home/liuwenmao/hook/libprog2.so /usr/sbin/sshd -D -p 9999

很奇怪的是sshd会有问题,root下面运行客户端会有错误:

root@node10:~/hook# LD_PRELOAD=/home/liuwenmao/hook/libprog2.so /usr/sbin/sshd -D -p 9999

#------客户端----
liuwenmao@node10:~$ ssh localhost -p 9999 -l test
test@localhost's password:
Permission denied, please try again.
test@localhost's password:
close[31596] 4
Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-78-generic x86_64)

  • Documentation: https://help.ubuntu.com
  • Management: https://landscape.canonical.com
  • Support: https://ubuntu.com/advantage

1 package can be updated.
0 updates are security updates.

*** System restart required ***
Last login: Fri Sep 29 15:51:02 2017 from ::1
write error: Bad file descriptor
write error: Bad file descriptor
write error: Bad file descriptor
write error: Bad file descriptor
test@node10:~$

但是在普通用户下sudo是ok的:

liuwenmao@node10:~/hook$ LD_PRELOAD=/home/liuwenmao/hook/libprog2.so sudo /usr/sbin/sshd -D -p 9999

#------客户端----
liuwenmao@node10:~$ ssh localhost -p 9999 -l test
test@localhost's password:
Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-78-generic x86_64)

  • Documentation: https://help.ubuntu.com
  • Management: https://landscape.canonical.com
  • Support: https://ubuntu.com/advantage

1 package can be updated.
0 updates are security updates.

*** System restart required ***
Last login: Fri Sep 29 15:51:20 2017 from ::1
test@node10:~$

猜测可能是ssh有一些suid等操作,对LD_PRELOAD有一些影响。

探讨

作为检测方来说,其实用户态函数hook并不是一种要的选择,因为
1. 攻击者可以检测环境变量,如果存在LD_PRELOAD,就可以认定有监测程序,作为处置方式也很简单,要不unset掉这个环境变量,这样检测方就无法hook了;要不直接放弃这台主机,避免被捕获下一步的攻击样本。
2. 检测方法并不可扩展,一方面,需要hook很多很多很多函数,例如新建文件描述符就涉及到open、accept、listen等函数,如果漏掉一个,就可能监控不全;另一方面,很多程序是非预期的,例如docker新启动一个容器,就可能会将所有的环境变量清空,这样默认场景下是无法监控到容器内部的操作的(虽然容器里的进程是docker run的子进程)
3. hook函数编写较为复杂,至少它调用的方法不能是hook函数之一。

Kubernetes对外服务

Kuberntets介绍

Kubernets是一个Google主导的机群管理系统,目前底层可以使用Docker,实现Docker实例的应用编排。Kubernets的介绍很多,本文简单介绍安装和使用的过程。更多资料可参考Kerbernets官网

Kuberntets安装

Kubernets可以在虚拟机VM或安装Linux的服务器上安装,本文以Ubuntu Server服务器为例,详细可参见官网的Ubuntu安装指南

先下载Kubernets源码,目前最新版为1.4.1

root@node3:/usr/src# git clone https://github.com/kubernetes/kubernetes.git
root@node3:/usr/src# cd /usr/src/kubernetes
root@node3:/usr/src# git checkout v1.4.1

本文中存在两个节点,node3(192.168.200.13)和node4(192.168.200.14),node3作为控制节点和计算节点,node4作为计算节点。于是修改kubernetes/cluster/ubuntu/config-default.sh

export nodes=${nodes:-"root@192.168.200.13 root@192.168.200.14"}
roles=${roles:-"ai i"}
export NUM_NODES=${NUM_NODES:-2}
export SERVICE_CLUSTER_IP_RANGE=${SERVICE_CLUSTER_IP_RANGE:-100.0.0.0/16}  # formerly PORTAL_NET
export FLANNEL_NET=${FLANNEL_NET:-172.16.0.0/16}
DNS_SERVER_IP=${DNS_SERVER_IP:-"100.0.0.2"}

以上就是对配置文件的全部改动,请放置在相应位置。然后进行安装:

$ cd kubernetes/cluster
$ KUBERNETES_PROVIDER=ubuntu ./kube-up.sh
...........
Validate output:
NAME                 STATUS    MESSAGE              ERROR
controller-manager   Healthy   ok
scheduler            Healthy   ok
etcd-0               Healthy   {"health": "true"}
Cluster validation succeeded
Done, listing cluster services:

Kubernetes master is running at http://192.168.200.13:8080

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

如果不出错则会提示安装完毕。此时将Kubernets的命令放于PATH中。

root@node3:/usr/src# export PATH=/usr/src/kubernetes/cluster/ubuntu/binaries/:$PATH
root@node3:/usr/src# which kubectl
/usr/src/kubernetes/cluster/ubuntu/binaries//kubectl

然后安装dashboard和dns组件:

$ cd cluster/ubuntu
$ KUBERNETES_PROVIDER=ubuntu ./deployAddons.sh

可能存在的问题:

  • 如果需要重装,请运行KUBERNETES_PROVIDER=ubuntu ./kube-down.sh,停掉相关服务,然后要还原/etc/default/docker配置文件。
  • Kubernets会从Google的镜像仓库(gcr.io)获取某些镜像,但国内被墙了,所以可以选择一个http代理服务器,并在需要启动这些镜像的主机上为docker添加代理,方法是在/etc/default/docker中的开头添加:
export HTTP_PROXY=...

然后重启docker:

service docker restart

完毕需要将代理去掉再重启docker,具体可参考这篇文章

确保所有需运行这些镜像的节点本地都要有这些镜像!!,可以先在一个节点上用代理下载所有镜像,然后上传到私有仓库,再在其他节点上下载这些镜像即可。

  • 在运行带有运行 google镜像时,如果本地已经有该镜像的时候,但配置文件中带有
    imagePullPolicy: Always
    时,则仍会从Google仓库去获取,一种方法是将其变为:
imagePullPolicy: IfNotPresent

另一种方法是放到私有仓库中。
* 如果配置文件中没有指定imagePullPolicy,老版本会优先从本地找该版本的镜像,如有则直接启动;但发现1.4.1版本会优先pull。测试需要在配置文件中加入:

然后再运行安装或其他的命令。

  • Kubernetes会修改/etc/default/docker,请注意不要被覆盖原来的一些配置,否则docker pull私有仓库可能有问题,我的配置是:
DOCKER_OPTS=" --registry-mirror=http://2687282c.m.daocloud.io -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock --bip=172.16.69.1/24 --mtu=1450"

Kubernets的一些概念

Pod

每一个Pod都是运行在某个节点上的实例或实例集合。可以对应于docker的实例或实例集合。
通过下面的命令可以查看运行的实例:

root@node3:~# kubectl get pods
NAME                   READY     STATUS    RESTARTS   AGE
django-default-yxm7u   1/1       Running   0          15m
django-x-q9twt         1/1       Running   0          15m
django-y-wgy0c         1/1       Running   0          15m
nginx-ingress-e049x    1/1       Running   0          14m

还可以看一些系统的实例:

root@node3:/usr/sr# kubectl get pods --namespace=kube-system
NAME                                READY     STATUS    RESTARTS   AGE
kube-dns-v20-h35xt                  3/3       Running   3          1h
kubernetes-dashboard-v1.4.0-5g12f   1/1       Running   6          13h

还可以看这些pod运行在什么节点,这对排查问题比较有用。

root@node3:/usr/src# kubectl get pods -o wide
NAME                   READY     STATUS    RESTARTS   AGE       IP            NODE
django-default-yxm7u   1/1       Running   0          20m       172.16.77.4   192.168.200.13
django-x-q9twt         1/1       Running   0          20m       172.16.9.2    192.168.200.14
django-y-wgy0c         1/1       Running   0          20m       172.16.9.3    192.168.200.14
nginx-ingress-e049x    1/1       Running   0          19m       172.16.77.5   192.168.200.13

Service

Service是提供对外可见的服务,可以使用下面的配置文件service.yaml新建服务。

# 3 Services for the 3 endpoints of the Ingress
apiVersion: v1
kind: Service
metadata:
  name: django-x
  labels:
    app: django-x
spec:
  type: NodePort
  ports:
  - port: 18111
    #nodePort: 30301
    targetPort: 8111
    protocol: TCP
    name: http
  selector:
    app: django-x

---
apiVersion: v1
kind: Service
metadata:
  name: django-default
  labels:
    app: django-default
spec:
  type: NodePort
  ports:
  - port: 18111
    #nodePort: 30302
    targetPort: 8111
    protocol: TCP
    name: http
  selector:
    app: django-default
---
apiVersion: v1
kind: Service
metadata:
  name: django-y
  labels:
    app: django-y
spec:
  type: NodePort
  ports:
  - port: 18111
    #nodePort: 30284
    targetPort: 8111
    protocol: TCP
    name: http
  selector:
    app: django-y

可以用kubectl查看服务(svc是services的缩写):

root@node3:/usr/src# kubectl create -f service.yaml
root@node3:/usr/src# kubectl get svc
NAME             CLUSTER-IP     EXTERNAL-IP   PORT(S)     AGE
basic            100.0.53.240    <nodes>       18112/TCP   8s
django-default   100.0.53.222   <nodes>       18111/TCP   21m
django-x         100.0.34.47    <nodes>       18111/TCP   21m
django-y         100.0.95.86    <nodes>       18111/TCP   21m
kubernetes       100.0.0.1      <none>        443/TCP     13h

正常情况下,在集群内部,是可以通过CLUSTER_IP : PORT来访问服务的,如在node3或node4上运行curl http://100.0.34.47:18111来访问django-x的服务的。

CLUSTER是Kubernets内部维护的机群,所以CLUSTER-IP和PORT是服务面向集群提供的内部ip和端口,互联网是访问不到的,例如上面的kubernets和basic服务就是这种情况。如果要从互联网访问这些服务,我们会在下面讲到,可用ingress的方法,使用一个代理将请求转到CLUSTER-IP : PORT。

如果每个物理节点是互联网可以直接访问到的话,那么也可以使用NodePort的类型,如上面的三个django都是这类服务,所以其外部IP是nodes。这样每个节点上的kube-proxy服务都会开一个端口P,外部网络可以通过访问任意一个节点的端口P进行访问。
那么P如何获得呢?可以通过查询服务获知:

root@node3:~/k8s-test# kubectl describe svc django-x
Name:           django-x
Namespace:      default
Labels:         app=django-x
Selector:       app=django-x
IP:         100.0.34.47
Port:           http    18111/TCP
NodePort:       http    32400/TCP
Endpoints:      172.16.76.3:8111
Session Affinity:   None
No events.

那么就可以通过curl http://192.168.200.13:32400来访问该服务了。

Replication Controller

Replication Controller(RC)是控制Pod数量和部署的控制器,Kubernets区别原生Docker很重要的一点是它实现了对资源的监控、弹性部署。假设一个pod挂了,rc可以再启动一个;或者机群要扩容,rc也可以很快增加pod实现。

以下配置文件rc.yaml可以新建三个RC:


# A single RC matching all Services apiVersion: v1 kind: ReplicationController metadata: name: django-x spec: replicas: 1 template: metadata: labels: app: django-x spec: containers: - name: django-x image: appstore:5000/liuwenmao/django-hello ports: - containerPort: 8111 --- apiVersion: v1 kind: ReplicationController metadata: name: django-default spec: replicas: 1 template: metadata: labels: app: django-default spec: containers: - name: django-default image: appstore:5000/liuwenmao/django-hello ports: - containerPort: 8111 --- apiVersion: v1 kind: ReplicationController metadata: name: django-y spec: replicas: 1 template: metadata: labels: app: django-y spec: containers: - name: django-y image: appstore:5000/liuwenmao/django-hello ports: - containerPort: 8111

如果要获取rc,可运行:

root@node3:/usr/src# kubectl create -f rc.yaml
root@node3:/usr/src# kubectl get rc
NAME             DESIRED   CURRENT   AGE
django-default   1         1         2h
django-x         1         1         2h
django-y         1         1         2h
nginx-ingress    1         1         2h

可能存在的问题
* 当要查看某个pod、service或rc的详细信息,可以用describe。如某个pod挂了,可以详细查看具体日志:

root@node3:/usr/src/kubernetes/cluster/ubuntu# kubectl get pods --namespace=kube-system
NAME                                READY     STATUS         RESTARTS   AGE
kube-dns-v20-0gnu3                  0/3       ErrImagePull   0          9m
kubernetes-dashboard-v1.4.0-5g12f   1/1       Running        4          11h

其中kube-dns看似有问题,继续检查:

root@node3:/usr/src/kubernetes/cluster/ubuntu# kubectl describe pods kube-dns-v20-0gnu3 --namespace=kube-system
Name:           kube-dns-v20-0gnu3
Namespace:      kube-system
Node:           192.168.200.14/192.168.200.14
Start Time:     Thu, 13 Oct 2016 09:56:24 +0800
Labels:         k8s-app=kube-dns
                version=v20
Status:         Pending
IP:             172.16.9.2
Controllers:    ReplicationController/kube-dns-v20
Containers:
  kubedns:
    Container ID:
    Image:              gcr.io/google_containers/kubedns-amd64:1.8
    Image ID:
    Ports:              10053/UDP, 10053/TCP
    Args:
      --domain=cluster.local.
      --dns-port=10053
    Limits:
      memory:   170Mi
    Requests:
      cpu:                      100m
      memory:                   70Mi
    State:                      Waiting
      Reason:                   ErrImagePull
    Ready:                      False
    Restart Count:              0
    Liveness:                   http-get http://:8080/healthz-kubedns delay=60s timeout=5s period=10s #success=1 #failure=5
    Readiness:                  http-get http://:8081/readiness delay=3s timeout=5s period=10s #success=1 #failure=3
    Environment Variables:      <none>
  dnsmasq:
    Container ID:
    Image:              gcr.io/google_containers/kube-dnsmasq-amd64:1.4
    Image ID:
    .......(略)
Events:
  FirstSeen     LastSeen        Count   From                    SubobjectPath                   Type            Reason          Message
  ---------     --------        -----   ----                    -------------                   --------        ------          -------
  6m            6m              1       {default-scheduler }                                    Normal          Scheduled       Successfully assigned kube-dns-v20-0gnu3 to 192.168.200.14
  5m            5m              1       {kubelet 192.168.200.14} spec.containers{kubedns}        Warning         Failed          Failed to pull image "gcr.io/google_containers/kubedns-amd64:1.8": image pull failed for gcr.io/google_containers/kubedns-amd64:1.8, this may be because there are no credentials on this request.  details: (Error response from daemon: {"message":"Get https://gcr.io/v1/_ping: dial tcp 64.233.188.82:443: i/o timeout"})
  4m            4m              1       {kubelet 192.168.200.14} spec.containers{dnsmasq}        Warning         Failed          Failed to pull image "gcr.io/google_containers/kube-dnsmasq-amd64:1.4": image pull failed for gcr.io/google_containers/kube-dnsmasq-amd64:1.4, this may be because there are no credentials on this request.  details: (Error response from daemon: {"message":"Get https://gcr.io/v1/_ping: dial tcp 64.233.188.82:443: i/o timeout"})
  3m            3m              1       {kubelet 192.168.200.14}                                 Warning         FailedSync      Error syncing pod, skipping: [failed to "StartContainer" for "kubedns" with ErrImagePull: "image pull failed for gcr.io/google_containers/kubedns-amd64:1.8, this may be because there are no credentials on this request.  details: (Error response from daemon: {\"message\":\"Get https://gcr.io/v1/_ping: dial tcp 64.233.188.82:443: i/o timeout\"})"
, failed to "StartContainer" for "dnsmasq" with ErrImagePull: "image pull failed for gcr.io/google_containers/kube-dnsmasq-amd64:1.4, this may be because there are no credentials on this request.  details: (Error response from daemon: {\"message\":\"Get https://gcr.io/v1/_ping: dial tcp 64.233.188.82:443: i/o timeout\"})"
, failed to "StartContainer" for "healthz" with ErrImagePull: "image pull failed for gcr.io/google_containers/exechealthz-amd64:1.2, this may be because there are no credentials on this request.  details: (Error response from daemon: {\"message\":\"Get https://gcr.io/v1/_ping: dial tcp 64.233.188.82:443: i/o timeout\"})"
]
  3m    3m      1       {kubelet 192.168.200.14} spec.containers{healthz}        Warning Failed          Failed to pull image "gcr.io/google_containers/exechealthz-amd64:1.2": image pull failed for gcr.io/google_containers/exechealthz-amd64:1.2, this may be because there are no credentials on this request.  details: (Error response from daemon: {"message":"Get https://gcr.io/v1/_ping: dial tcp 64.233.188.82:443: i/o timeout"})
  1m    1m      1       {kubelet 192.168.200.14} spec.containers{dnsmasq}        Warning Failed          Failed to pull image "gcr.io/google_containers/kube-dnsmasq-amd64:1.4": image pull failed for gcr.io/google_containers/kube-dnsmasq-amd64:1.4, this may be because there are no credentials on this request.  details: (Error response from daemon: {"message":"Get https://gcr.io/v1/_ping: dial tcp 64.233.189.82:443: i/o timeout"})
  4m    1m      2       {kubelet 192.168.200.14} spec.containers{healthz}        Normal  Pulling         pulling image "gcr.io/google_containers/exechealthz-amd64:1.2"
  59s   59s     1       {kubelet 192.168.19.14} spec.containers{healthz}        Warning Failed          Failed to pull image "gcr.io/google_containers/exechealthz-amd64:1.2": image pull failed for gcr.io/google_containers/exechealthz-amd64:1.2, this may be because there are no credentials on this request.  details: (Error response from daemon: {"message":"Get https://gcr.io/v1/_ping: dial tcp 64.233.189.82:443: i/o timeout"})
  59s   59s     1       {kubelet 192.168.200.14}                                 Warning FailedSync      Error syncing pod, skipping: [failed to "StartContainer" for "kubedns" with ErrImagePull: "image pull failed for gcr.io/google_containers/kubedns-amd64:1.8, this may be because there are no credentials on this request.  details: (Error response from daemon: {\"message\":\"Get https://gcr.io/v1/_ping: dial tcp 64.233.189.82:443: i/o timeout\"})"
, failed to "StartContainer" for "dnsmasq" with ErrImagePull: "image pull failed for gcr.io/google_containers/kube-dnsmasq-amd64:1.4, this may be because there are no credentials on this request.  details: (Error response from daemon: {\"message\":\"Get https://gcr.io/v1/_ping: dial tcp 64.233.189.82:443: i/o timeout\"})"
, failed to "StartContainer" for "healthz" with ErrImagePull: "image pull failed for gcr.io/google_containers/exechealthz-amd64:1.2, this may be because there are no credentials on this request.  details: (Error response from daemon: {\"message\":\"Get https://gcr.io/v1/_ping: dial tcp 64.233.189.82:443: i/o timeout\"})"

能看到是因为向google的仓库下载超时导致的,可参考上面的代理方法解决。

比如现在我需要运行一个django的web服务,那么可以通过运行下面的配置文件即可实现。

为Service添加互联网入口

Load balance

可以expose deployment(带–type=”LoadBalance”)的方式将服务暴露出去,但是目前这种方式支持公有云,如Google Container Engine等,貌似不能应用于私有的数据中心。具体可以参考官网Hello World的Allow external traffic一节

Ingress

在内网部署服务,希望对外暴露,可以使用Ingress的方式,以下配置文件为将上述服务在80端口上做映射,实现虚拟主机的功能。

# An Ingress with 2 hosts and 3 endpoints
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: echomap
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - path: /foo
        backend:
          serviceName: django-x
          servicePort: 18111
  - host: bar.baz.com
    http:
      paths:
      - path: /bar
        backend:
          serviceName: django-y
          servicePort: 18111
      - path: /foo
        backend:
          serviceName: django-x
          servicePort: 18111

然后运行并查看Ingress

root@node3:/usr/src/gohome/src/k8s.io/contrib/ingress/controllers/nginx-alpha# kubectl create -f rc.yaml
replicationcontroller "nginx-ingress" created
root@node3:~$ kubectl get ing
NAME      HOSTS                     ADDRESS   PORTS     AGE
echomap   foo.bar.com,bar.baz.com             80        3d
root@node3:~$ kubectl describe ing echomap
Name:           echomap
Namespace:      default
Address:
Default backend:    default-http-backend:80 (<none>)
Rules:
  Host      Path    Backends
  ----      ----    --------
  foo.bar.com
            /foo    django-x:18111 (<none>)
  bar.baz.com
            /bar    django-y:18111 (<none>)
            /foo    django-x:18111 (<none>)
Annotations:
No events.

Ingress是Kubernets的一种用于访问服务的机制,可以通过api获得这些映射关系,不过如何实现具体的功能,例如上例中的虚拟主机功能,可以使用Kubernets的contrib中的ingress-controller实现,本文给出的是使用nginx实现。

contrib是一大堆未进入Kubernets核心的代码集合,代码在(https://github.com/kubernetes/contrib),安装请按照项目的README进行(需要在$GOPATH里面,不是随便安装即可的),假设$GOPATH=/usr/src/gohome,那么Kubernets Contrib在/usr/src/gohome/src/k8s.io/contrib/,而我们说的nginx Ingress控制器则在/usr/src/gohome/src/k8s.io/contrib/ingress/controllers/nginx-alpha。

查看一下nginx-alpha目录下面的rc.yaml可知,Ingress Controller后台使用了gcr.io/google_containers/nginx-ingress镜像,不过这个镜像在笔者测试时有问题,所以实验中还是根据同目录下的Dockerfile重新生成的。查看Dockerfile可知,这个镜像是基于nginx的,并通过运行controller程序将Kubernets API获得的Ingress映射翻译成nginx的配置文件,从而实现了反向代理到运行不同网站的Service的功能。

当运行完创建rc.yaml后,我们可以找到相应的docker容器,观察其中的映射规则:

root@node3:/usr/src# kubectl get pods -o wide|grep nginx
nginx-ingress-g518r    1/1       Running   2          3d        172.16.66.3   192.168.200.13
root@node3:/usr/src# docker ps |grep nginx
4374a4965333        gcr.io/google_containers/nginx-ingress:0.1                                    "/controller"            46 hours ago        Up 46 hours                                                                                                                                                            k8s_nginx.a9cb3eb9_nginx-ingress-g518r_default_71b457b9-914e-11e6-821d-c81f66f3c543_f9c7501f
0051bb8806d1        gcr.io/google_containers/pause-amd64:3.0                                      "/pause"                 46 hours ago        Up 46 hours         0.0.0.0:80->80/tcp                                                                                                                                 k8s_POD.6cfd0339_nginx-ingress-g518r_default_71b457b9-914e-11e6-821d-c81f66f3c543_e01f441e
root@node3:/usr/src# docker exec -it 4374a4965333 /bin/bash
[ root@nginx-ingress-g518r:/etc/nginx ]$ ls
certs/   fastcgi.conf    koi-utf  mime.types  proxy_params  sites-available/  snippets/     win-utf
conf.d/  fastcgi_params  koi-win  nginx.conf  scgi_params   sites-enabled/    uwsgi_params
[ root@nginx-ingress-g518r:/etc/nginx ]$ cat nginx.conf

events {
  worker_connections 1024;
}
http {
  # http://nginx.org/en/docs/http/ngx_http_core_module.html
  types_hash_max_size 2048;
  server_names_hash_max_size 512;
  server_names_hash_bucket_size 64;



  server {
    listen 80;
    server_name foo.bar.com;

    location /foo {
      proxy_set_header Host $host;
      proxy_pass http://django-x.default.svc.cluster.local:18111;
    }
  }
  server {
    listen 80;
    server_name bar.baz.com;

    location /bar {
      proxy_set_header Host $host;
      proxy_pass http://django-y.default.svc.cluster.local:18111;
    }
    location /foo {
      proxy_set_header Host $host;
      proxy_pass http://django-x.default.svc.cluster.local:18111;
    }
  }
[ root@nginx-ingress-g518r:/etc/nginx ]$ ping django-x.default.svc.cluster.local
PING django-x.default.svc.cluster.local (100.0.87.12) 56(84) bytes of data.
64 bytes from django-x.default.svc.cluster.local (100.0.87.12): icmp_seq=1 ttl=47 time=265 ms
64 bytes from django-x.default.svc.cluster.local (100.0.87.12): icmp_seq=2 ttl=47 time=253 ms

到此为止,背后的原理已经清楚了。我们可以看一下运行效果。因为三个服务运行的都是django,所以可以查看django的输出日志(如果用supervisor运行的,可以进容器查看/var/log/supervisor里面相关的log文件)查看真实的Web访问情况。

此时,如果访问http://bar.baz.com/bar,就会转到django-y的服务(http://django-y.default.svc.cluster.local:18111),如果访问http://bar.baz.com/foo或http://foo.bar.com/foo,就转到django-x,其余的则转到ngix的404页面。

上述思路不仅可以使用在web服务,还可以用于如外部接入ssh等。

Django国际化

本文在django 1.7验证过

1 在项目settings.py中添加两行(+表示添加行):


+ LOCALE_PATHS = (BASE_DIR+ "/locale",)

MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.locale.LocaleMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',

2 在views.py中标记要翻译的字符串,格式为{% trans ‘字符串’%} 如

{% load i18n %}


  • {% trans 'User' %}
  • 3 建立locale目录结构

    $ mkdir locale/en -p
    $ mkdir locale/zh_CN -p
    $ python manage.py makemessages -a
    processing locale zh_CN
    processing locale en

    此时会新建./locale/{lang}/LC_MESSAGES/django.po文件,lang表示语言,中文为zh_CN。
    其中django.po文件包含了django目录中所有需要翻译的字符串。如

    #, fuzzy
    msgid ""
    msgstr ""
    "Project-Id-Version: PACKAGE VERSION\n"
    "Report-Msgid-Bugs-To: \n"
    "POT-Creation-Date: 2016-04-07 10:33+0800\n"
    "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    "Last-Translator: FULL NAME \n"
    "Language-Team: LANGUAGE \n"
    "Language: \n"
    "MIME-Version: 1.0\n"
    "Content-Type: text/plain; charset=UTF-8\n"
    "Content-Transfer-Encoding: 8bit\n"

    #: templates/base.html:138
    msgid "User"
    msgstr ""

    4 翻译相关语言下的django.po。对于每个msgid的字符串,修改对应的翻译语句,如上例为

    #: templates/base.html:138
    msgid "User"
    msgstr ""

    5 编译:
    $ python manage.py compilemessages
    processing file django.po in /usr/src/appstoreserver/appstore/locale/zh_CN/LC_MESSAGES
    processing file django.po in /usr/src/appstoreserver/appstore/locale/en/LC_MESSAGES

    6 done

    创新沙盒:“软件定义安全”不再是实验室产物

    自从著名咨询机构Gartner在《The Impact of Software-Defined Data Centers on Information Security》 一文中提出软件定义安全(Software Defined Security,SDS)的概念后,软件定义与安全的结合已成为业界的前沿发展热点。背后的原因很直观:软件定义安全强调了安全控制平面与数据平面分离,从而在控制平面上可灵活调整应用策略,快速变更安全业务。

    在安全可被软件定义后,新的安全业务可在企业网络中可新上线,特别是在数据中心中,可实现计算、存储、网络和安全的弹性控制,实现软件定义的数据中心SDDC。正是因为这些优秀的特性,解决了企业客户长期面临的安全管理和运营“痛点”,软件定义安全自从开始就引起了学术界和工业界极大的关注。

    各大厂商都开始做相关的研究和研发工作,RSA大会一直是厂商们展现自己最新工作的舞台。如Check Point在RSA 2014大会上宣布推出软件定义防护(Software Defined Protection,SDP)革新性安全架构,可在当今日新月异的IT和威胁环境中为企业提供虚拟化的边界防护。赛门铁克也在RSA 2015提出使用软件定义网络技术对APT攻击进行取证的话题也提供了一种安全事件事后快速分析的新思路 。

    而RSA大会开到了第25个年头时,我们惊喜地发现更多的公司在展示在软件定义安全的领域的工作,特别是在体现创新的Innovation Sandbox(创新沙盒)竞赛中,10家经过专业评审的公司,居然有3家与这个话题有关,分别在不同的方面做出了开创性的工作。
    如Versa Networks公司,强调在软件定义广域网(SD-WAN)和分支(Branch)网络的环境中,通过虚拟化网络功能(VNF)技术,将各种各样异构的网络功能编程通用的组件,可快速在相应的网络中部署,大大减少了企业部署相应业务的开销,提高了整个过程的敏捷程度。

    Skyport Systems公司同样也是为企业提供高效的安全计算基础设施,但按照传统建立边界思维,攻击者在进入系统内部后就容易进一步攻击内部其他重要资源。该公司的逻辑是,所有的资源都是零信任,这样即便内部某资源被攻破,那么从该点作为跳板进一步攻击也是困难的。那么这里就涉及到软件定义的访问控制,例如如何做到“零信任”条件下各处的访问控制策略快速调整。该公司在B轮融资中获得3000万美元。

    再如Phantom Cyber公司认为在大量出现攻击的场景下,花费大量的人力去发现解决问题已不太现实。与前两个公司不同,Phantom Cyber从应用层入手,构建自动化、可编排的安全应用体系。它支持多种主流的数据分析平台,可利用较为高层的脚本实现安全运维自动化。

    当然除了这些初创公司,还有很多公司也在基于自身产品做相关的工作。如在29日的Session环节,VMWare的安全产品部门SVP Tom Corn就演示了在NSX的环境中,如何可按需定义微分段(MicroSegmentation),并对任意APP间快速添加加密处理。厂商展示区域,Catbird公司的软件定义安全架构 通过微分区(Micro-Segmentation)在虚拟环境中划分不同的区域,并通过编排将安全策略下发给多种类型的安全设备,并作用在区域级别或虚拟机级别。这些工作都体现了各家在成熟产品线通过软件定义做了很多延展性的工作。

    绿盟科技自2013年开始研究SDN和软件定义安全,研发了包括软件定义的抗DDoS、流量异常检测和Web安全等原型系统,并在2015年发布了软件定义安全的白皮书,探讨在该领域的进展。

    创新沙盒中10个产品中出现了三个能体现SDS的产品,笔者认为其背后的原因有几个:其一,作为软件定义安全的支撑技术,如VNF/NFV、SDN方案,在国外已经有一些成熟的应用,如NSX已经代替Vsphere成为VMWare成长最快的产品,Cisco的ACI方案也与很多安全厂商有合作;其二,企业的高效安全运营需求,直接催生了安全编排这些应用层面的创新;其三,也是最重要的,出于企业对降低成本的天然需求,软件定义的理念转换为实际产品的动力十足。

    RSA大会的创新沙盒一直是硅谷安全行业的风向标,今年的沙盒竞赛体现了软件定义安全确实不只是一些实验室的原型系统,一些初创企业已经开始将其作为重点,根据企业在安全运营方面出现的存在各种问题,有针对性的提出了自己的解决方案。我们有理由做出判断,软件定义安全恐怕离真正的产品化和商用已经不远了。

    UPDATE:创新沙盒的获胜者已公布,即Phantom。除去其他领域的竞争者,Phantom能在上述其他两家公司脱颖而出,笔者认为主要是因为它从客户的业务作为切入点,强调了业务层面的编排,且一开始便是开放架构设计,可支持第三方的VNF组成服务链。给客户安全运维提供高效手段,降低运维成本的同时,打消了客户被厂商锁定(Vendor Lock-in)的顾虑,从而拥有更好的商业模式(事实上,该公司在种子轮融资中获得了270万美元)。

    ssh翻墙简单说明

    1 购买vps。设置用户名user和密码pass
    2 新建ssh profile。以xshell为例,新建一个profile,输入vps的ip、port、user和pass
    3 建立隧道。在profile属性->连接->SSH->隧道->添加,类型为Dynamic,侦听端口1080。同时启用转发x11(不知是否需要),保存profile。

    vps-tunnel

    4 连接vps,默认情况下连接成功。存在两 会等时候,可能被封掉…
    5 安装浏览器扩展,如firefox的foxyproxy
    6 以foxyproxy为例,新建一个proxy,URL匹配模式为要翻墙的url,如*google*,代理服务器细节中选择手动配置代理服务器,主机为127.0.0.1,端口1080,socks代理。确认
    foxyproxy

    7 启用该proxy,即可正常翻墙

    使用cloud-init实现虚拟机信息管理

    为什么要用cloud-init

    不同种类的设备VM启动总是一件非常麻烦的事情,例如安全设备有WAF、IPS等,每种设备的网络接口、启动脚本互不一样,即便同一种设备,其主机名、网络地址等也不一样。那么如何对这些VM启动过程进行管理,并完成所有数据的配置呢?

    在这之前,我的实习生是怎么做的:将一台VM的管理口网络地址设置为192.168.2.100,然后每次启动实例之后定时访问http://192.168.2.100/somepath,当成功访问这个页面之后,使用REST接口配置该机器的IP地址为所需的新地址(如200.0.0.2);这个时候网络会短暂不同,然后在访问http://200.0.0.2/somepath,当成功访问之后,接下来配置各种值。 整个过程比较麻烦,所有的配置都需要实现REST接口,无法做到自定义启动脚本的更新;最不可接受的是,这个过程是串行的,当要启动100个VM时,只能一个VM一个VM顺序启动,否则两个VM都有同一个地址(192.168.2.100),那么网络访问就可能出现问题了。 不过受到各种Stack管理虚拟机用到cloud-init的启发,我认为我们也可以使用这套工具实现上述过程的。

    什么是cloud-init

    cloud-init(简称ci)在AWS、Openstack和Cloudstack上都有使用,所以应该算是事实上的云主机元数据管理标准。那么问题来了,google相关的文档,发现中文这方面几乎没有,Stacker你们再搞虾米呢?当然话说回来英文的资料除了官网外几乎也没有什么,我花了近一周的时间才弄明白了。

    首先要明确的是cloud-init在工作之前,VM是从DHCP服务器获取到了IP,所有DHCP发现不是cloud-init的事情。当你在Openstack中用ubuntu cloud VM启动卡在cloud-init界面时,多半是因为DHCP还没获取IP,而不是cloud-init本身的问题。那么cloud-init主要走什么呢?它向一台数据服务器获取元数据(meta data)和用户数据(user data),前者是指VM的必要信息,如主机名、网络地址等;后者是系统或用户需要的数据和文件,如用户组信息、启动脚本等。当cloud-init获取这些信息后,开始使用一些模块对数据进行处理,如新建用户、启动脚本等。

    cloud-init工作原理

    首先,数据服务器开启HTTP服务,cloud-init会向数据服务器发送请求,确认数据源模块,依次获取版本、数据类型和具体数据内容信息。

    确认数据源模块

    cloud-init会查找/etc/cloud/cloud.cfg.d/90_dpkg.cfg中的datasource_list变量,依次使用其中的数据源模块,选择一个可用的数据源模块。如我的配置文件中:datasource_list: [ Nsfocus, NoCloud, AltCloud, CloudStack, ConfigDrive, Ec2, MAAS, OVF, None ],那么ci首先调用$PYTHON_HOME/dist-packages/cloudinit/sources/DataSourceNsfocus.py中类DataSourceNsfocus的get_data函数,当且仅当访问链接DEF_MD_URL为正常时,这个数据源被认为是OK的。

    在我的实践中,CloudStack的DEF_MD_URL为DHCP的服务器ip,而Openstack和AWS则为一个常值169.254.169.254,然后在宿主机的中做一个iptables重定向,这样就到了我们的服务器监听端口8807:

    $ sudo ip netns exec ns-router iptables -L -nvx -t nat
    Chain PREROUTING (policy ACCEPT 169850 packets, 21565088 bytes)
        pkts      bytes target     prot opt in     out     source               destination         
          47     2820 REDIRECT   tcp  --  *      *       0.0.0.0/0            169.254.169.254      tcp dpt:80 redir ports 8807
    $ sudo ip netns exec ns-router iptables -L -nvx
    Chain INPUT (policy ACCEPT 97027 packets, 8636621 bytes)
        pkts      bytes target     prot opt in     out     source               destination         
           0        0 ACCEPT     tcp  --  *      *       0.0.0.0/0            127.0.0.1            tcp dpt:8807
    

    一些系统假设

    需要说明的是,虽然每个数据源访问的入口都是get_data,但每个数据服务的格式和位置是不一样的,元数据可能在/nsfocus/latest/metadata/,也可能在/latest/metadata.json,也就是说数据源模块根据自己系统的规定,访问相应的数据,并根据ci的规定,指定如何将这些数据与ci接下来的处理模块对应上。

    那么我们的数据访问地址是这样的:

    --namespace
               |
               |------version
                            |
                            |---------meta_data.json
                            |---------meta_data
                            |                  |---------public-hostname
                            |                  |---------network_config
                            |
                            |---------user_data
    

    其中,namespace为nsfocus,meta_data.json是一个json文件,里面包含所有元数据。
    其次,我们的数据服务器IP为111.0.0.2

    获得元数据

    因为获取是HTTP的形式,所以以curl为例说明下面过程:

    $ curl http://111.0.0.2/nsfocus
    1.0
    latest
    $ curl http://111.0.0.2/nsfocus/latest
    meta_data
    user_data
    meta_data.json
    $ curl http://111.0.0.2/nsfocus/latest/meta_data
    public-hostname
    local-ipv4
    network_config
    ...
    $ curl http://111.0.0.2/nsfocus/latest/meta_data/local-ipv4
    111.0.0.11
    $ curl http://111.0.0.2/nsfocus/latest/meta_data.json
    {"files": {}, "public_keys": {"controller": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCxtEfzf8I0jA7IHDRHJtDq3nTcTXAWgFYEsAV0i7WU6v8gvFr/R+DTvkVdFGgbM/qhVNWUehmPicENac6xldbL5ov6J7c8Y+UytPwJCt13IzDHXaL1BxVYUV6dpe6SYGYohNQ2KZYkG/95NzjxI1Max5DDvU8mbpEz/KyphowseburknQTkOTEigJ7CKM4G1eGVhBHKRHXbNsoPZwJnqvIHIpDcwGaj+OgVGF+o3ytH4twrwNwUFiWrUaxo9j2uRTSejYRh1eC9KOYXTnXInzV1xCVHYs/x+eIzav+2oM8hgR3xr1efgSU2sMzXrp+mJAPzHaAyAat+s7AMDu9tKrd marvel@marvel-ThinkPad-X230"}, "hostname": "waf-ba-0001", "id": "waf-ba-0001", "network_config": {"content_path": "latest/meta_data/network_config"}}
    

    这个meta_data.json是我们参考Openstack的标准,自己实现的。当获得meta_data.json后,DataSourceNsfocus解析里面的字段,填入自己的数据结构中,如放入DataSourceNsfocus的result字典中。

                if found and translator:
                    try:
                        data = translator(data)
                    except Exception as e:
                        raise BrokenMetadata("Failed to process "
                                             "path %s: %s" % (path, e)) 
                if found:
                    results[name] = data
    

    这样,如hostname就存为self.result[‘meta’][‘hostname’]。

    供其他处理模块使用的获取元数据函数

    在上一阶段,元数据的提供、获取和存储都是很自由的,那么这些数据怎么被使用,例如hostname怎么设置呢?那就需要根据ci的标准实现一些接口,如设置hostname就需要我们实现DataSourceNsfocus的get_hostname方法:

        def get_hostname(self, fqdn=False):
            return self.metadata.get("hostname")
    

    这样,其他模块如set_hostname和update_hostname就会使用这个方法正确设置主机名了。如果你想设置其他数据,可参考cloud-init数据源参考的介绍。了解还有哪些处理模块,可读一下/etc/cloud/cloud.cfg文件。

    至此,一些VM所需的常用配置已经搞定,那么如果我们想做一些流程方面的自动下发和运行该怎么做呢?则需要设置一下user_data。

    获取用户数据

    用户数据包括几类:

    • 配置文件(Cloud Config Data),类型为Content-Type: text/cloud-config,系统配置文件,如管理用户等,与/etc/cloud下的cloud.cfg最后合并配置项,更多的配置细节参考 配置样例
    • 启动任务(Upstart Job),类型为Content-Type: text/upstart-job,建立Upstart的服务
    • 用户数据脚本(User-Data Script),类型为Content-Type: text/x-shellscript,用户自定义的脚本,在启动时执行
    • 包含文件(Include File),类型为Content-Type: text/x-include-url,该文件内容是一个链接,这个链接的内容是一个文件,
    • (Cloud Boothook),类型为Content-Type: text/cloud-boothook,
    • 压缩内容( Gzip Compressed Content),
    • 处理句柄(Part Handler),类型为Content-Type: text/part-handler,内容为python脚本,根据用户数据文件的类型做相应的处理
    • 多部分存档(Mime Multi Part archive),当客户端需要下载多个上述用户数据文件时,可用Mime编码为Mime Multi Part archive一次下载

    实例

    我在data目录下面建立三个文件:

    cloud.config

    groups:
      - nsfocus: [nsfocus]
    users:
      - default
      - name: nsfocus
        lock-passwd: false 
        sudo: ALL=(ALL) NOPASSWD:ALL
    system_info:
      default_user:
        name: nsfocus
        groups: [nsfocus,sudo]
    bootcmd:
      - echo "#HOSTS\n127.0.0.1    localhost\n::1    localhost ip6-localhost\nff02::1    ip6-allnodes\nff03::1    ip6-allrouters\n#ip#    #host#" > /etc/hosts
    runcmd:
      - [echo, "RUNCMD: welcome to nsfocus-------------------------------------------"]
    final_message: "Welcome to NSFOCUS SECURITY #type#====================================="
    

    这是一个cloud-config文件,内容表示新建一个nsfocus的用户,归于nsfocus和sudo组,在启动时运行bootcmd的命令更新hosts,启动最后输出final_message。

    nsfocus-init.script

    $ cat nsfocus-init.script 
    #!/bin/bash
    echo "this is a startup script from nsfocus" 
    echo "this is a startup script from nsfocus" >> /tmp/nsfocus-init-script
    

    这是一个测试脚本,在系统启动时会被调用

    nsfocus-init.upstart

    $ cat nsfocus-init.upstart 
    description "a nsfocus upstart job"
    
    start on cloud-config
    console output
    task
    script
    echo "====BEGIN======="
    echo "HELLO From nsfocus Upstart Job, $UPSTART_JOB"
    echo "HELLO From nsfocus Upstart Job, $UPSTART_JOB" >> /tmp/hello
    echo "=====END========"
    end script
    

    这是一个测试访问,在系统启动时会被启动

    HTTP服务器收到/nsfocus/latest/user_data时,作如下处理:

    from email.mime.multipart import MIMEMultipart
    from email.mime.text import MIMEText
    def encode_mime(fps):
        combined_message = MIMEMultipart()
        for fn, patterns in fps:
            print fn
            (filename, format_type) = fn.split(":", 1)
            print filename
            print "---"
            with open(filename) as fh: 
                contents = fh.read()
            for (p, v) in patterns:
                contents = contents.replace(p, v)
        
            sub_message = MIMEText(contents, format_type, sys.getdefaultencoding())
            sub_message.add_header('Content-Disposition', 'attachment; filename="%s"' % (filename[filename.rindex("/")+1:]))
            combined_message.attach(sub_message)
        return str(combined_message)
    
    #main process
    #....blablabla
          if subtype == "user_data":
                if len(arr) == 0:
                    res = encode_mime([
                        ("./data/nsfocus-init.upstart:upstart-job",[]),
                        ("./data/nsfocus-init.script:x-shellscript",[]),
                        ("./data/cloud.config:cloud-config",[('#ip#', device.management_ip), ('#host#',device.id), ('#type#', device.type)])])
                    return self.gen_resp(200, res)
    

    虚拟机启动之后,服务器收到请求,返回下面的内容:

    From nobody Fri Dec 26 15:34:36 2014
    Content-Type: multipart/mixed; boundary="===============5883341837158849895=="
    MIME-Version: 1.0
    
    --===============5883341837158849895==
    MIME-Version: 1.0
    Content-Type: text/upstart-job; charset="us-ascii"
    Content-Transfer-Encoding: 7bit
    Content-Disposition: attachment; filename="nsfocus-init.upstart"
    
    description "a nsfocus upstart job"
    
    start on cloud-config
    console output
    task
    script
    echo "====BEGIN======="
    echo "HELLO From nsfocus Upstart Job, $UPSTART_JOB"
    echo "HELLO From nsfocus Upstart Job, $UPSTART_JOB" >> /tmp/hello
    echo "=====END========"
    end script
    
    --===============5883341837158849895==
    MIME-Version: 1.0
    Content-Type: text/x-shellscript; charset="us-ascii"
    Content-Transfer-Encoding: 7bit
    Content-Disposition: attachment; filename="nsfocus-init.script"
    
    #!/bin/bash
    echo "this is a startup script from nsfocus" 
    echo "this is a startup script from nsfocus" >> /tmp/nsfocus-init-script
    
    --===============5883341837158849895==
    MIME-Version: 1.0
    Content-Type: text/cloud-config; charset="us-ascii"
    Content-Transfer-Encoding: 7bit
    Content-Disposition: attachment; filename="cloud.config"
    
    groups:
      - nsfocus: [nsfocus]
      - dev
    users:
      - default
      - name: nsfocus
        lock-passwd: false 
        sudo: ALL=(ALL) NOPASSWD:ALL
    system_info:
      default_user:
        name: nsfocus
        groups: [nsfocus,sudo]
    bootcmd:
      - echo "#HOSTS\n127.0.0.1    localhost\n::1    localhost ip6-localhost\nff02::1    ip6-allnodes\nff03::1    ip6-allrouters\n111.0.0.12    waf-ba-0001" > /etc/hosts
    runcmd:
      - [echo, "RUNCMD: welcome to nsfocus-------------------------------------------"]
    
    final_message: "Welcome to NSFOCUS SECURITY waf====================================="
    
    --===============5883341837158849895==--
    

    VM启动界面打印如下信息,且主机名变成了我们预定的值,说明确实获取meta-data和user-data成功,脚本运行也成功了。不过要说明一点,upstart在Ubuntu上没问题,但Debian没通过,可能当前阶段Debian的启动机制还有一些区别,所以还是使用bootcmd或启动脚本的方式启动。

    Ubuntu登陆页面

    参考文献

    cloud-init数据源参考 http://cloudinit.readthedocs.org/en/latest/topics/datasources.html
    dnsmasq参考 http://www.thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html
    更多样例 https://github.com/number5/cloud-init/blob/master/doc/examples/