转载 | Alpine Linux,一个只有5M的Docker镜像

Alpine Linux Docker镜像基于Alpine Linux操作系统,后者是一个面向安全的轻型Linux发行版。不同于通常Linux发行版,Alpine Linux采用了musl libc和busybox以减小系统的体积和运行时资源消耗。在保持瘦身的同时,Alpine Linux还提供了自己的包管理工具apk,可以在其网站上查询,或者直接通过apk命令查询和安装。

Alpine Linux Docker镜像也继承了Alpine Linux发行版的这些优势。相比于其他Docker镜像,它的容量非常小,仅仅只有5M,且拥有非常友好的包管理器。

下表是一些官方镜像的大小:

镜像名称 大小(MB)
ubuntu:latest 187.9
debian:latest 125.1
centos:latest 196.6
alpine 4.794

除了小,Alpine镜像的另外一大优势就是内置完整包管理器。相较于其他微型基础镜像(如busybox,基础镜像大小为1.113MB),拥有一个包管理器,可以快速构建应用镜像。例如这个dnsmasq镜像,Dockerfile非常简单,仅仅运行了Alpine提供的apk工具安装了dnsmasq包即可:

FROM alpine:3.2
RUN apk -U add dnsmasq
EXPOSE 53 53/udp
ENTRYPOINT ["dnsmasq", "-k"]

使用

由于Alpine Linux有完整的包管理器,其使用方式和其他的基础镜像类似,直接使用其包管理命令apk即可。

如README中例子,如果需要安装一个mysql客户端,只需要创建如下Dockerfile:

FROM gliderlabs/alpine:3.3
RUN apk add --no-cache mysql-client
ENTRYPOINT ["mysql"]

然后通过docker build命令,即可构建出自己的mysql客户端。同样,基于Alpine Linux构建出来的镜像,有其空间上的巨大优势:

docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
alpine/mysql        latest              edf988b8f4c8        58 seconds ago      35.74 MB

争论

对于Alpine Linux,Hacker News上争论还是比较激烈的。

首先是空间占用问题,小是Alpine Linux的最大优势,但是Docker的文件系统可以进行分层缓存,对于已经构建或者拉取过镜像的机器来说,每次的增量更新内容可能并不会很多。也就是说,如果所有镜像都使用相同的基础镜像,这个镜像在所有机器上都只会拉取一遍。

另外,Alpine Linux使用了musl,可能和其他Linux发行版使用的glibc实现会有所不同。在容器化中最可能遇到的是DNS问题,即musl实现的DNS服务不会使用resolv.conf文件中的search和domain两个配置,这对于一些通过DNS来进行服务发现的框架可能会遇到问题。

总结

Alpine Linux,一个只有5M的Docker镜像,它尽可能的简化了镜像的大小,易于分发,有着完善的包管理器和预编译的包。如果你需要一个干净、简洁的容器,开始尝试使用吧!

转载自 infoQ


Linux 命令之basename、dirname

basename命令用于去掉文件名的目录和后缀,dirname命令用于截取目录

path dirname basename
“/usr/lib” “/usr” “lib”
“/usr/” ”/” “usr”
“usr” ”.” “usr”
”/” ”/” ”/”
”.” ”.” ”.”
”..” ”.” ”..”

语法

basename String [ Suffix ]
dirname [ Option ] Name

basename 命令读取 String 参数,删除以 /(斜杠) 结尾的前缀以及任何指定的 Suffix 参数,并将剩余的基本文件名称写至标准输出。dirname则相反。

代码实例

#!/bin/sh  
  
# 跳转到脚本所在目录  
cd $(dirname "$0") || exit 1  
// cd `dirname $0`

man文档

basename

NAME
       basename - strip directory and suffix from filenames

SYNOPSIS
       basename NAME [SUFFIX]
       basename OPTION... NAME...

DESCRIPTION
       Print NAME with any leading directory components removed.  If specified, also remove a trailing SUFFIX.

       Mandatory arguments to long options are mandatory for short options too.

       -a, --multiple
              support multiple arguments and treat each as a NAME

       -s, --suffix=SUFFIX
              remove a trailing SUFFIX; implies -a

       -z, --zero
              end each output line with NUL, not newline

       --help display this help and exit

       --version
              output version information and exit

EXAMPLES
       basename /usr/bin/sort
              -> "sort"

       basename include/stdio.h .h
              -> "stdio"

       basename -s .h include/stdio.h
              -> "stdio"

       basename -a any/str1 any/str2
              -> "str1" followed by "str2"

dirname

NAME
       dirname - strip last component from file name

SYNOPSIS
       dirname [OPTION] NAME...

DESCRIPTION
       Output each NAME with its last non-slash component and trailing slashes removed; if NAME contains no /'s, output '.' (meaning the current directory).

       -z, --zero
              end each output line with NUL, not newline

       --help display this help and exit

       --version
              output version information and exit

EXAMPLES
       dirname /usr/bin/
              -> "/usr"

       dirname dir1/str dir2/str
              -> "dir1" followed by "dir2"

       dirname stdio.h
              -> "."

php 报错 date default timezone get

今天在使用satis生成网页

php bin/satis build satis.json public/

报了如下的错误:

[Twig_Error_Runtime]
An exception has been thrown during the rendering of a template ("date_default_timezone_get(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone.").

[ErrorException]
date_default_timezone_get(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. 

解决方法:

修改php.ini配置文件(我的路径为C:\my_pp\php\php-5.5.30-nts-Win32-VC11-x64\php.ini)

在php.ini配置文件中找到: ;date.timezone =

date.timezone = "Asia/Shanghai"

修改完后,重启apache/nginx。


使用 laravel 的 queryScope 处理 relations

本文是 Laravel scope 的两个应用技巧。在官方文档 5.1 的文档中给出的是这样的描述。

全局作用域(Global Scopes)可让你定义有限制的共用集合,它可以轻松地在你的应用程序中被重复使用。例如,你可能需要频繁地获取所有被认为是「受欢迎的」用户。要定义此范围,则可以简单地在 Eloquent 模型方法前面加上前缀 scope. 它总是返回查询构造器的实例.

scope 的便利之处在于在繁杂的数据中确定出数据间的逻辑关系。在简单的应用中,按照官方文档,scope 已经很好满足了我们的要求了。在下面的例子中,我在 trait 中定义了一系列具有共性的 scope:

namespace App\Supports;

use Carbon\Carbon;

trait ScopeTrait
{
    /**查询创建时间
     * 使用 prefix 是历史原因使用了 leftjoin.
     * @param $query
     * @return mixed
     */
    public function scopeCreatedAt($query, $start = '', $end = '', $equal = true)// start <= target < end
    {
        $className = get_class($this);
        $prefix = $className::TABLE . '.';
        if ($start) {
            $compare = $equal ? '>=' : '>';
            $query->where($prefix .'created_at', $compare, $start);
        }
        if ($end) {
            $query->where($prefix .'created_at', '<', $end);
        }
        return $query;
    }
    
    public function scopeSource($query, $source)
    {
        $className = get_class($this);
        $prefix = $className::TABLE . '.';
        if ($source) {
            $query->where($prefix . 'source', '=', $source);
        }
        return $query;
    }

    public function scopeYesterday($query)
    {
        $start = Carbon::yesterday();
        $end = Carbon::today();
        return $query->createdAt($start, $end);
    }
}

我们通过这个方法查询某个时间段内表的有效数据。假定 Account 表中使用了这个 trait,需要查找昨天创建的帐号,可以这么使用:

$accounts = Account::yesterday()->get();

在 laravel Eloquent ORM 中还经常用到 with 这一方法来关联表。普通的使用场景也很简单,例如

class Talent{
    public function account()
    {
        return $this->belongsTo('App\Models\Account', 'account_uuid');
    }
}

使用 Talent::with(‘account’) 就可以获取到关联数据。如果希望leftjoin,可以在行内使用如下语句实现:

$yesterdayCreateTalent = Talent::with(['account' => function ($q) {
  $q->yesterday();
}])->get();

也可以拆分方法进行使用。

// Talent
public function test()
{
   return $this->belongsTo('App\Models\Account', 'account_uuid')->yesterday();
   // or
   // return $this->account()->yesterday();
}

参考资料


PHP 的字符串和数组的转换函数 explode() 和 implode()

一年一度的春节终于过去啦๑乛◡乛๑ 。今年春节一直比较闲,终于有闲心下来看一些架构方面的书了。最近已经习惯于用微信读书了,感觉不愧是腾讯做的东西。读书应用里我用过的很多,包括京东,豆瓣,Amazon,多看阅读,百度阅读,都有用过,而且为之付费不菲,断断续续看了几本书,没办法养成习惯。一部分原因可以归咎于自己确实没有毅力,另一部分感觉还是 app 做的有欠缺。

微信读书在这方面做的感觉很到位,背靠腾讯大靠山,首先流量不缺,通过首批核心用户在朋友圈网络进行宣传推广,在此之上的推广还能得到5个书币,获得的书币可以拿来买书;除了推广获得书币外,还可以通过阅读时间来兑换,不得不说这个方式太赞了,反过来又促进了爱书用户更多地看书。除了看书的核心功能,腾讯理所当然还加入了社交功能社交功能(爱恨交加的社交,说好的用完即走呢,傲娇的张小龙,╮(╯3╰)╭),相比于其它几种阅读app,微信做的实在是太赞了。看好微信读书的未来。

另外今天发现了一个有趣的网站http://hepwori.github.io/execorder/,哈哈哈,反正效果是这样的:

好了,下面是正文。

explode()

把字符串打散为数组。

语法

explode(separator,string,limit)

* separator	必需。规定在哪里分割字符串。
* string	必需。要分割的字符串。
* limit	    可选。规定所返回的数组元素的数目。
            大于 0 - 返回包含最多 limit 个元素的数组
            小于 0 - 返回包含除了最后的 -limit 个元素以外的所有元素的数组
            0 - 返回包含一个元素的数组

例子:

$str = "Hello world. I love Shanghai!";
var_dump(explode(" ",$str));

真实例子:

public function setExpectedIndustryTagAttribute($value)
{
    if (is_string($value)) {
        $array = explode(' ', $value);
        $array = array_filter($array);
        $this->attributes['tag'] = json_encode($array);
    }

    if (is_array($value)) {
        $this->attributes['tag'] = $value;
    }
}

implode()

把数组元素组合为字符串。

语法

implode(separator,array)

* separator	可选。规定数组元素之间放置的内容。默认是 ""(空字符串)。
* array	必需。要组合为字符串的数组。

例子:

$arr = array('Hello','World!','I','love','Shanghai!');
echo implode(" ",$arr)."<br>";
echo implode("+",$arr)."<br>";
echo implode("-",$arr)."<br>";
echo implode("X",$arr);

Docker pull 出现的 TLS handshake timeout

一直以来都是使用国外的服务器,因而对 docker 速度慢并没有特别的感觉。然而在本地做测试,还是需要docker,于是就出现了这个问题了。

docker pull voduytuan/jenkins-php-docker

... ...
error pulling image configuration: Get https://dseasb33srnrn.cloudfront.net/registry-v2/docker/registry/v2/blobs/sha256/f7/f7fbb8679343e6cbf232ca1ecbe4fd019748a50046cc391411719c52c865bf5a/data?Expires=1488249661&Signature=DgwydePkO~fs0pg3CPbf3GCtC05-n--9-1kO0XRpqKZLAobNcEWnTTEnSD8SSk1QevOQPk6jMFda4YEMJOQGXSrf4AxwAOt~VzgwWSLXKfq9u4gu0gxghsiOzsQ4MNBS3Kk9ZXJWuW3iqcs9G1LkGhW7-yHmhlu0-yEEKD9DeUE_&Key-Pair-Id=APKAJECH5M7VWIS5YZ6Q: net/http: TLS handshake timeout 好在 DaoCloud 发布了国内的镜像仓库,解决了这个问题。

$ echo "DOCKER_OPTS=\"\$DOCKER_OPTS --registry-mirror=http://f2d6cb40.m.daocloud.io\"" | sudo tee -a /etc/default/docker
$ sudo service docker restart

重启docker服务后,再次push,就完全ok了。

做到这一步已经够用了。如果想使用daocloud的升级版,还可以安装DaoCloud Toolbox,速度更是飞起来。

更新:

docker 官方已经也做了国内的镜像源地址,具体修改方式参考这里:CentOS 源与 Docker 源加速的设置

参考资料


一些运维技巧的备忘

计划任务cron

计划任务不必要在直接写在底层的 crontab 中,可以在 crontab 中这么设置

将运行脚本文件保存到 /var/local/cron 目录中,区分好时间和用户进行管理。

ssh相关

ssh除了登录端口修改外,在当前用户的家目录下 ~/.ssh 添加 config 文件,用于快速登录其他服务器或 scp 进行文件传输。

Host    tokyo
  HostName        xx.xx.xx.xx
  Port            1234
  User            kelu
  IdentityFile    ~/.ssh/xxx
Host    fremont
  HostName        xx.xx.xx.xx
  Port            1234
  User            madcat
  IdentityFile    ~/.ssh/xxx

除此之外,还可以修改 /etc/hosts,用于快速ping某某网站等等。

  127.0.0.1       localhost
  xx.xx.xx.xx   tokyo
  xx.xx.xx.xx   aliyun