存档

文章标签 ‘Linux’

我是怎样使用 Docker 来帮助 X 系统上的开发工作的(译)

2014年8月20日 没有评论

作者:SEBASTIAN GĘBSKI
译者:brightzhou

对应用进行设置可能会相当复杂,尤其是在 Linux 系统上。不用的应用有不同的配置方法,它们会在不同的文件系统路径下(在不同的 Linux 发行版中,由于应用存在多种变种,这些路径也会有所不同)保存二进制文件和数据。一旦你把系统配置好了,就很难再恢复到之前的状态,尤其是你同时进行了一些其他修改的时候(比如,安装了一些其他应用程序)。这就是为什么最近一些部署工具,比如 Puppet、 Chef、 Ansible 和 Salt,这么流行的原因。但即使有了这些工具的帮助,创建 cookbook/recipe 也许也十分麻烦: Linux 系统并不是以傻瓜化著称的,系统本身并不会阻止你干任何事情。

业界出现了一个新的工具

很幸运的是,对于 Linux 运维人员来说,一个新的工具产生了,而该工具很有可能改变游戏规则:Docker,一个开源的平台,该平台能够以一种轻量级的方式打包应用程序以及它们的依赖。

这到底意味着什么?

Docker 使你能够在 Linux 系统上对不同的应用程序进行隔离,在不同的上下文环境中运行这些程序(这些程序可能执行在一台物理机器上,也可能运行在不同的物理机器上),请记住,这一点是非常重要的:

  • Docker 使用了底层的内核机制做到了资源隔离,而并不需要其他资源消耗型的虚拟化技术,如果想了解更多细节,请点击这里。
  • Docker 镜像(一些保存的快照)以及 Docker 容器(运行时隔离应用程序的容器)与虚拟镜像相比,使用起来快多了。
  • 容器十分灵活,你可以在容器里打包很多应用,你也可以只打包一个,同时你可以尽你所愿来运行容器。
  • Docker 容器里运行着一些镜像,而这些镜像之上有一个层的概念,分层使得你能够很容易地构造你的应用程序(每次操作都可以很容易地回滚/前滚,你只需要简单地增加或是删除层,却不会对下面的层产生影响)。
  • Docker 完美地支持了“一次配置,到处运行”的范式。

在实践中 Docker 是如何工作的?

或者这么讲,至少对于我来说,它是这么工作的:

  1. 我已经在我的本地 Vagrant 环境中指定了 Docker 作为部署工具,Vagrant 从 1.6 版本就引入了这一功能。我相信我不用过多地解释什么是 Vagrant,以及为什么它是 X 平台开发人员必备的工具。
  2. Docker 已经为我自动下载了一些 Linux 发行版的镜像(这些镜像会被 Vagrant 使用,在 hypervisor 上运行)。
  3. 现在我能够以至少两种方式创建我自己的容器(正在运行的,实现资源隔离的应用程序):
    • 第一种方式是制作一个用命令配置好的 Dockerfile ,这个 Dockerfile 基于干净的 Linux 镜像来生成,同时这些命令也使用了非常简单的 DSL (领域特定语言)。这种方式是我比较喜欢的,而且比较有道理。
    • 第二种方式是创建一个运行着终端的全新的容器,这样的话你就可以在终端上执行你自己的命令,来做到你自己想做的事情。
sudo docker build
...
or
...
sudo docker run -i -t <image_name> /bin/bash    

有些重要的事情需要记住

  1. 如果在 Dockerfile 里的命令执行完了,或者是通过 run 这个子命令运行的命令执行完了(因为它们并不是 daemon 程序),那么容器就会关闭并且消失!
  2. 当容器正在运行时,你可以十分方便地:
    • 查看容器中命令的输出(docker logs
    • 挂载到运行的容器(docker attach
    • 列出容器内文件系统的实际变化(记得不同的容器并不能看到其他容器的变化!)(docker diff
    • 暴露并且映射容器中的端口(比如,如果你正在搭建一个应用程序,而该应用程序在容器内已经有了相应的端口)(在 dockerfile 中的 EXPOSE 选项,Docker 命令的 -p 选项)
  1. 如果你想要通过手动执行命令的方式创建你自己的容器(run),你需要存储你自己的镜像 – 首先你需要在正在运行的容器列表里找到你想要的容器,然后执行commit命令:
sudo docker ps
sudo commit <container name>

通过使用以上所有的选项…

…我能够:

  • 使用应用组件的任何组合来组成我的本地开发环境,添加或者删除一个运行时组件就如同开启或者关闭容器那么简单。一旦我关闭了某个应用组件,那么它也从文件系统中完全消失了。
  • 非常方便地(回滚/前滚)创建出独立的,隔离的应用容器,同时又不像虚拟镜像那样会耗费许多时间,也没有不必要的操作系统开销。
  • 实验一系列很有意思的事情,却几乎不会冒重头来过的风险(有鉴于此,Chef recipe已经过时了…)。

…在我做到以上所有事情的同时,我的操作系统却坚如磐石:我不会破坏任何事情,我可以很简单地回滚我做的任何操作。安装其他的容器也不会互相影响。

总之,在几周的时间里,我已经把 Docker 作为了我的主要的软件开发工具。现在我已经无法想象缺少了 Docker 我该怎样进行基于 JVM 的开发工作了


这篇文章由 SEBASTIAN GĘBSKI 撰写,brightzhou 翻译。点击 这里 阅读原文。
The article was contributed by SEBASTIAN GĘBSKI, translated by brightzhou, click here to read the original publication.
分类: 信息技术 标签: ,

Bash 快捷键

2011年10月23日 没有评论

转载的~~推荐几个常用的bash快捷键~~

“棕色粗体”表示“我推荐的”!

Ctrl-A 相当于HOME键,用于将光标定位到本行最前面

Ctrl-E 相当于End键,即将光标移动到本行末尾

Ctrl-B 相当于左箭头键,用于将光标向左移动一格

Ctrl-F 相当于右箭头键,用于将光标向右移动一格

Ctrl-D 相当于Del键,即删除光标所在处的字符

Ctrl-K 用于删除从光标处开始到结尾处的所有字符

Ctrl-L 清屏,相当于clear命令

Ctrl-R 进入历史命令查找状态,然后你输入几个关键字符,就可以找到你使用过的命令

Ctrl-U 用于删除从光标开始到行首的所有字符。一般在密码或命令输入错误时常用

Ctrl-H 删除光标左侧的一个字符

Ctrl-W 用于删除当前光标左侧的一个单词

Ctrl-P 相当于上箭头键,即显示上一个命令

Ctrl-N 相当于下箭头键,即显示下一个命令

Ctrl-T 用于颠倒光标所在处字符和前一个字符的位置。(目前不知道有什么作用,哪位朋友知道?)

Ctrl-J 相当于回车键

Alt-. 用于提取历史命令中的最后一个单词。你先执行history命令,然后再敲击此快捷键若干下,你就懂了!

Alt-BackSpace 用于删除本行所有的内容,基本上和Ctrl-U类似。

Alt-C 用于将当前光标处的字符变成大写,同时本光标所在单词的后续字符都变成小写。

Alt-L 用于将光标所在单词及所在单词的后续字符都变成小写。

Alt-U 用于将光标所在单词的光标所在处及之后的所有字符变成大写。

ps:使用bind -P命令可以查看所有键盘绑定。

ps2:Alt快捷键较少使用,因为常常和编辑器冲突

over~

分类: 服务器管理 标签: , ,

将程序所需的第三方库静态编译进可执行文件中

2011年7月11日 没有评论

这两天由于项目的需要,需要将一个第三方库libxml2编译进可执行文件中,最终能够在没有安装libxml2的linux机器上运行所编写的程序。

我编写的程序是一个日志记录模块,有三个很简单的文件,log.c,log.h,main.c。log.c负责实现日志记录的相应接口,main.c则调用log.c编译生成的库文件使用这个接口来记录日志。

首先查阅网上资料以后,知道了gcc的-static选项可以完成这一功能,同时需要注意的是使用-static选项时需要将-lxml2选项放在gcc命令的最后。

所以正常情况下,如下的命令即可完成我的需求。(lxml2依赖于pthread,z,m库)

gcc -c log.c -I/usr/include/libxml2 -L/usr/lib -lxml2
 
ar -r liblog.a log.o
 
gcc -static main.c -o main -I/usr/include/libxml2 -L. -llog -L/usr/lib -lxml2 -lpthread -lz -lm

但是非常不幸的是,这样子编译出来的可执行文件运行后会出现段错误。但是如果不加上static选项进行编译,那么编译出来的二进制文件可以运行。

然后郁闷了半个小时,在网上搜了很多的资料,皇天不负有心人,终于被我找到原因了。

由于我是使用的虚拟机下面的Ubuntu8.04进行相关的开发工作的,所以在安装libxml2的开发包时就直接用了apt-get install。但是,非常悲剧的是,该版本下的libxml2存在一个与线程相关的bug。

该bug的描述如下,from Jerome

i just found out an issue with libxml2 and static linking. It is occuring on ubuntu 8.04.

  1. take a libxml2 example from the official website at http://xmlsoft.org/examples/tree2.c
  2. you compile it and link it in static gcc -g -static -Wl,–start-group `xml2-config –cflags –libs –static` tree2.c -Wl,–end-group -o tree2
  3. and then ./tree2 and you get core dump

oopsa not happy jerome not happy

LATER: before i was using ubuntu feisty 7.04 and all was fine. so i looked at launchpad and took 2.6.27.dfsg-1ubuntu3version. recompiled it myself under hardy and linked it statically. no more crash. oopsa. and i filled a bug for it

解决方案如上所述,但是查看了bug列表后,我又找到了一个更简单的解决方案

The answer is simple, don’t link threaded programs statically, there are

many reasons why it is a wrong thing to do.

If you really must do so, the safest solution is
-Wl,–whole-archive -lpthread -Wl,–no-while-archive

所以最后,我的最后一个编译命令变为了

 gcc -static main.c -o main -I/usr/include/libxml2 -L. -llog -L/usr/lib -lxml2 -Wl,--whole-archive-lpthread -Wl,--no-whole-archive -lz -lm

 

分类: C 标签: , , ,

用find,xargs,tar命令可选备份

2010年1月12日 2 条评论

今天写了一个脚本要把server上所有的文件重新处理一下,由于没仔细检查就在server上跑了,导致server上所有的文件改错了,而且这个过程不可逆,幸好在另外一个server上面有备份,于是就想着把备份移过来,刚开始移的时候直接tar命令,没想到要恢复的那个目录下面还有很多pdf,mp3,ppt…文件,估计大概有20多个G,而我的脚本只改动了文本文件,于是,用find加tar试了一下,可是没想到对文件名中存在空格的文件会出错,搜索了一下,找到了一个好办法。

基本的用法比如:

find . -name '*.htm' | xargs tar czvf backup.tar.gz

一般情况,上面这个命令运行的很好,但是如果找到的文件名代空格,上面的命令运行就可能会出问题了。

find有一个参数-print0,于默认的-print相比,输出的序列不是以空格分隔,而是以null字符分隔。而xargs也有一个参数-0,可以接受以null而非空格间隔的输入流。所以说xargs简直就是为find而生的。上面的问题就很好解决了:

find . -name '*.htm' -print0 | xargs -0 tar czvf backup.tar.gz

Linux 2.6内核下实现一个操作/proc的内核模块

2009年5月18日 1 条评论

Linux课程实践的一次作业,要求在linux下构建一个在/proc中实现clock文件的模块,该文件支持读操作。
然后开始疯狂的google操作。进而发现整个流程应该是这样的
如果是ubuntu或者fedora发行版,需要配置对应的内核开发环境,而SUSE Enterprise则不用。我用的是ubuntu,在这里就简要的介绍一下ubuntu下内核开发环境的配置。
首先需要安装源build-essential这个包,对应的命令为

sudo apt-get install build-essential

然后再去下载自己的版本所对应的Linux源代码
查看自己的Linux版本的命令为

uname -a

我自己的显示是2.6.27-14-generic
所以应该去下载2.6.27的Linux源代码,命令为:

sudo apt-get install linux-source-2.6.27

好,等这个包安装完成以后,我们进入/usr/src这个目录下将其解压缩,因为默认是把这个文件下载到这个目录下的,你也可以拷贝这个文件到任何你想要放置的位置进行解压
解压完成以后,进入该目录,进行编译操作,命令如下
Step 1:

make mrproper

一下,这个动作是将你使用的源代码包恢复到初始状态
Step 2:
配置内核
保存原有系统配置文件,将/boot/下的config-2.6.27-14-generic拷贝到/usr/src/linux-source-2.6.27(刚下载下来解压的Linux源码下,并把名字改为.config)
对应命令为

sudo cp /boot/config-2.6.27-14-generic /usr/src/linux-source-2.6.27
sudo mv config-2.6.27-14-generic .config

接着进行内核的配置

sudo apt-get install libncurses5-dev

(make menuconfig要调用的)

make menuconfig

当你make menuconfig后,选倒数第二项:Load an alternate configuration file
把.config加载进来,这样你就能在原来内核的基础之上修改了
Step3:

make

让他编译吧,耐心等待,我笔记本性能比较差,要编译两个小时左右的时间
Step4:

make bzImage

Step5:

make modules

Step6:

make modules_install

Step7:

make install

重新启动
至此,内核树就建立啦,我们就可以进行相应的内核模块的开发了
贴上自己的clock.c文件,该文件读取内核时间,然后在/proc目录下创建一个clock文件,在clock文件里打印出系统自1970年1月1日起的秒数和纳秒数,中间用空格隔开

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/time.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#define PROCFS_NAME	"clock"
MODULE_LICENSE("GPL");
MODULE_AUTHOR("conan");
 
static struct proc_dir_entry *clock_proc_file;
 
//called when a read is done on /proc/my_clock
int read_time(char *buf,char **start,off_t offset,int count,int *eof,void *data){
 
  struct timespec now;
  int len = 0;
  if (offset > 0)
  {
    printk(KERN_INFO "offset %d : /proc/%s : read_time, \
       wrote %d Bytes\n", (int)(offset),PROCFS_NAME, len);
    *eof = 1;
    return len;
  }
  now = current_kernel_time();
  len = sprintf(buf, "%ld %ld\n", now.tv_sec, now.tv_nsec);
  printk(KERN_INFO "now time is %s\n",buf);
  return len;
}
 
 
//called when module is inserted in system
int init_module(){
  int ret_value = 0;
  clock_proc_file=create_proc_entry(PROCFS_NAME,0644,NULL);
  clock_proc_file->read_proc = read_time;
  printk(KERN_INFO "Trying to create /proc/clock:\n");
  if (clock_proc_file == NULL)
  {
    ret_value = -ENOMEM;
    printk(KERN_INFO "Error: Could not initialize /proc/%s\n",PROCFS_NAME);
  }
  else
  {
    printk(KERN_INFO "Initialize /proc/%s\n",PROCFS_NAME);
  }
  return ret_value;
}
//called to remove module from system
void cleanup_module(){
  remove_proc_entry(PROCFS_NAME,NULL);
  printk(KERN_INFO "/proc/%s removed\n",PROCFS_NAME);
}

接着是这个文件的Makefile
obj-m := clock.o
KDIR = /usr/src/linux-source-2.6.27/
all:
$(MAKE) -C $(KDIR) M=$(PWD)
clean:
rm -f *.mod.c *.mod.o *.ko *.o *.tmp_versions
编译的时候千万要注意Makefile和clock.c(不要改名,除非对应的Makefile也改)一定要在一个名字中没有空格的文件夹中,否则就会出现没有找到对应规则的错误!我搞了近两个小时才发现了这个问题。
编译完成之后使用命令insmod clock.ko装载该模块,这个时候就可以
cat /var/log/syslog查看相应的输出信息了,如果模块被正确装载,则会在这个文件的底部出现相应的调试信息。
接着,再是我的测试程序,这个程序对/proc/clock进行了读取,并将读取的值与gettimeofday获取的值作比较。
代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#define MAX 10
#define LENGTH 21
 
int main(){
   int i;
   FILE *my_clock;
   struct timeval gtodTimes[MAX];
   char procClockTimes[MAX][LENGTH];
 
   printf("Please wait for a while, test is running...\n");
   //getting times from both module and system call
   for(i=0;i<MAX;i++){
      my_clock = fopen("/proc/clock", "r");
      if (my_clock == NULL)
      {
         printf("/proc/clock doesn't exist\n");
         exit(1);
      }
      else
      {
         gettimeofday(&gtodTimes[i], NULL);
         fgets(procClockTimes[i], LENGTH, my_clock);
         fclose(my_clock);
         sleep(1);
      }
   }
   //printing data
   for(i=0;i<MAX;i++){
      char *nsec_pointer;
      char *delim = " ";
      nsec_pointer = strtok(procClockTimes[i],delim);
      nsec_pointer = strtok(NULL,delim);
      printf("At iteration %d, Gettimeofday=%lds %ldus, myClock=%ss %sns\n", i,
             gtodTimes[i].tv_sec, gtodTimes[i].tv_usec ,procClockTimes[i],nsec_pointer);
   }
   return(0);
}

Linux VIRTT定时器设置时间与初始时间不一致的问题

2009年5月13日 没有评论

首先,来看一段测试程序,该程序设定了一个virtual 的定时器,然后用getitimer()得到当前的定时器的值
#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
struct itimerval virtt;
virtt.it_interval.tv_sec = 1;
virtt.it_interval.tv_usec = 0;
virtt.it_value.tv_sec = 1;
virtt.it_value.tv_usec = 0;
setitimer(ITIMER_VIRTUAL, &virtt, NULL);

while (1)
{
getitimer(ITIMER_VIRTUAL,&virtt);
printf("tv_sec %ld\n",virtt.it_value.tv_sec);
printf("tv_usec %ld\n",virtt.it_value.tv_usec);
}
return 0;
}

运行程序得到测试结果如下:
tv_sec 1
tv_usec 4062
tv_sec 1
tv_usec 4062
tv_sec 1
tv_usec 4062

//好多好多重复
tv_sec 1
tv_usec 62
tv_sec 1
tv_usec 62
tv_sec 1
tv_usec 62

//好多好多重复
tv_sec 0
tv_usec 996062
tv_sec 0
tv_usec 996062
tv_sec 0
tv_usec 996062
tv_sec 0
tv_usec 996062
//…
//下面继续递减4000us(一个jiffy的值)
OK,为什么明明设置的是1s的定时器,怎么变成了1.004062s了,这多出来的是4062哪来的?让我们看一下Linux的源代码
首先找到do_setitimer这个函数,setitimer调用的就是这个函数,贴个代码片段:
int do_setitimer(int which, struct itimerval *value, struct itimerval *ovalue)
{
struct task_struct *tsk = current;
struct hrtimer *timer;
ktime_t expires;
cputime_t cval, cinterval, nval, ninterval;

/*
* Validate the timevals in value.
*/
if (!timeval_valid(&value->it_value) ||
!timeval_valid(&value->it_interval))
return -EINVAL;

switch (which) {
case ITIMER_REAL:
...
break;
case ITIMER_VIRTUAL:
nval = timeval_to_cputime(&value->it_value);//使用我们自己设置的定时器的数值,返回一个long赋值给nval
ninterval = timeval_to_cputime(&value->it_interval);
spin_lock_irq(&tsk->sighand->siglock);
cval = tsk->signal->it_virt_expires;
cinterval = tsk->signal->it_virt_incr;
if (!cputime_eq(cval, cputime_zero) ||//如果nval或者cval的值不为0
!cputime_eq(nval, cputime_zero)) {
if (cputime_gt(nval, cputime_zero))//如果nval的值大于0,则给nval加上一个jiffy的时间,这就解释了为什么一开始设置的0一下子多了4000us,那还有多出来的62us是哪来的,让我们再来看一下上面的timeval_to_cputime函数是干什么的,请转到下一段代码片段
nval = cputime_add(nval,
jiffies_to_cputime(1));
set_process_cpu_timer(tsk, CPUCLOCK_VIRT,//开始设置进程的计时器,并将其挂到相应的队列
&nval, &cval);
}
tsk->signal->it_virt_expires = nval;
tsk->signal->it_virt_incr = ninterval;
spin_unlock_irq(&tsk->sighand->siglock);
if (ovalue) {
cputime_to_timeval(cval, &ovalue->it_value);
cputime_to_timeval(cinterval, &ovalue->it_interval);
}
break;
case ITIMER_PROF:
...
default:
return -EINVAL;
}
return 0;
}

下面是timeval_to_cputime的代码片段:

unsigned long sec = value->tv_sec;
long usec = value->tv_usec;

if (sec >= MAX_SEC_IN_JIFFIES){
sec = MAX_SEC_IN_JIFFIES;
usec = 0;
}
return (((u64)sec * SEC_CONVERSION) +
(((u64)usec * USEC_CONVERSION + USEC_ROUND) >>//这里有一个复杂的运算操作,这个值就是上面的nval,具体的宏定义以及这段代码的意思可以去看源码上面的说明
(USEC_JIFFIE_SC - SEC_JIFFIE_SC))) >> SEC_JIFFIE_SC;

紧接着,我们再来研究一下do_getitimer,和do_setitimer相反,这个函数通过调用cputime_to_timeval函数将当前定时器的值赋值给应用程序的timeval结构,下面是代码片段:

u32 rem;

value->tv_sec = div_u64_rem((u64)jiffies * TICK_NSEC,
NSEC_PER_SEC, &rem);
value->tv_usec = rem / NSEC_PER_USEC;//这里有一个除法操作,看到这里我猜多出来的62us就是因为上面的复杂运算和这个除法操作共同导致的

OK,现在我们已经从实现上了解了为什么我们设置的定时器一开始的时候与设置的值不同,那么,为什么Linux要这么设计呢?
贴一段因为英文说明:
Implementations may place limitations on the timer value. To make sure that a process gets at least as much time as requested, the timer value is rounded up to the next timer tick (a timer tick is the smallest supported value).
The timer value is rounded up to the next timer tick because, the timer will be initialize somewhere between timer ticks.
If a setitimer() is followed by a getitimer() without a timer tick in between, it is possible that the value returned by getitimer() may be more than the initial value requested by setitimer() due to this rounding.
简单的来说,就是Linux想让一个进程的计时开始时间一定要大于等于我们自己设置的时间。