标签 开发环境 下的文章

大家好!我又写了一篇关于 我最喜欢的电脑工具 的文章。这一篇讲的是 Docker Compose!

本文主要就是讲一讲我对 Docker Compose 有多么满意啦(不讨论它的缺点)!咳咳,因为它总能够完成它该做的,并且似乎总能有效,更棒的是,它的使用还非常简单。另外,在本文中,我只讨论我是如何用 Docker Compose 来搭建开发环境的,而不涉及它在生产中的使用。

最近,我考虑了很多关于这种个人开发环境的搭建方式,原因是,我现在把所有的计算工作都搬到了一个私有云上,大概 20 美元/月的样子。这样一来,我就不用在工作的时候花时间去思考应该如何管理几千台 AWS 服务器了。

在此之前,我曾花了两天的时间,尝试使用其他的工具来尝试搭建一个开发环境,搭到后面,我实在是心累了。相比起来,Docker Compose 就简单易用多了,我非常满意。于是,我和妹妹分享了我的 docker-compose 使用经历,她略显惊讶:“是吧!你也觉得 Docker Compose 真棒对吧!” 嗯,我觉得我应该写一篇博文把过程记录下来,于是就有了你们看到的这篇文章。

我们的目标是:搭建一个开发环境

目前,我正在编写一个 Ruby on Rails 服务(它是一个计算机“调试”游戏的后端)。在我的生产服务器上,我安装了:

  • 一个 Nginx 服务器
  • 一个 Rails 服务
  • 一个 Go 服务(使用了 gotty 来代理一些 SSH 连接)
  • 一个 Postgres 数据库

在本地搭建 Rails 服务非常简单,用不着容器(我只需要安装 Postgres 和 Ruby 就行了,小菜一碟)。但是,我还想要把匹配 /proxy/* 的请求的发送到 Go 服务,其他所有请求都发送到 Rails 服务,所以需要借助 Nginx。问题来了,在笔记本电脑上安装 Nginx 对我来说太麻烦了。

是时候使用 docker-compose 了!

docker-compose 允许你运行一组 Docker 容器

基本上,Docker Compose 的作用就是允许你运行一组可以互相通信 Docker 容器。

你可以在一个叫做 docker-compose.yml 的文件中,配置你所有的容器。我在下方将贴上我为这个服务编写的 docker-compose.yml 文件(完整内容),因为我觉得它真的很简洁、直接!

version: "3.3"
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: password # yes I set the password to 'password'
  go_server:
    # todo: use a smaller image at some point, we don't need all of ubuntu to run a static go binary
    image: ubuntu
    command: /app/go_proxy/server
    volumes:
      - .:/app
  rails_server:
    build: docker/rails
    command: bash -c "rm -f tmp/pids/server.pid && source secrets.sh && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/app
  web:
    build: docker/nginx
    ports:
      - "8777:80" # this exposes port 8777 on my laptop

这个配置包含了两种容器。对于前面两个容器,我直接使用了现有的镜像(image: postgresimage: ubuntu)。对于后面两个容器,我不得不构建一个自定义容器镜像,其中, build: docker/rails 的作用就是告诉 Docker Compose,它应该使用 docker/rails/Dockerfile 来构建一个自定义容器。

我需要允许我的 Rails 服务访问一些 API 密钥和其他东西,因此,我使用了 source secrets.sh,它的作用就是在环境变量中预设一组密钥。

如何启动所有服务:先 “build” 后 “up”

我一直都是先运行 docker-compose build 来构建容器,然后再运行 docker-compose up 把所有服务启动起来。

你可以在 yaml 文件中设置 depends_on,从而进行更多启动容器的控制。不过,对于我的这些服务而言,启动顺序并不重要,所以我没有设置它。

网络互通也非常简单

容器之间的互通也是一件很重要的事情。Docker Compose 让这件事变得超级简单!假设我有一个 Rails 服务正在名为 rails_server 的容器中运行,端口是 3000,那么我就可以通过 http://rails_server:3000 来访问该服务。就是这么简单!

以下代码片段截取自我的 Nginx 配置文件,它是根据我的使用需求配置的(我删除了许多 proxy_set_headers 行,让它看起来更清楚):

location ~ /proxy.* {
  proxy_pass http://go_server:8080;
}
location @app {
  proxy_pass http://rails_server:3000;
}

或者,你可以参考如下代码片段,它截取自我的 Rails 项目的数据库配置,我在其中使用了数据库容器的名称(db):

development:
  <<: *default
  database: myproject_development
  host: db # <-------- 它会被“神奇地”解析为数据库容器的 IP 地址
  username: postgres
  password: password

至于 rails_server 究竟是如何被解析成一个 IP 地址的,我还真有点儿好奇。貌似是 Docker 在我的计算机上运行了一个 DNS 服务来解析这些名字。下面是一些 DNS 查询记录,我们可以看到,每个容器都有它自己的 IP 地址:

$ dig +short @127.0.0.11 rails_server
172.18.0.2
$ dig +short @127.0.0.11 db
172.18.0.3
$ dig +short @127.0.0.11 web
172.18.0.4
$ dig +short @127.0.0.11 go_server
172.18.0.5

是谁在运行这个 DNS 服务?

我(稍微)研究了一下这个 DNS 服务是怎么搭建起来的。

以下所有命令都是在容器外执行的,因为我没有在容器里安装很多网络工具。

第一步::使用 ps aux | grep puma,获取 Rails 服务的进程 ID。

找到了,它是 1837916!简单~

第二步::找到和 1837916 运行在同一个网络命名空间的 UDP 服务。

我使用了 nsenter 来在 puma 进程的网络命令空间内运行 netstat(理论上,我猜想你也可以使用 netstat -tupn 来只显示 UDP 服务,但此时,我的手指头只习惯于打出 netstat -tulpn)。

$ sudo nsenter -n -t 1837916 netstat -tulpn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.11:32847        0.0.0.0:*               LISTEN      1333/dockerd
tcp        0      0 0.0.0.0:3000            0.0.0.0:*               LISTEN      1837916/puma 4.3.7
udp        0      0 127.0.0.11:59426        0.0.0.0:*                           1333/dockerd

我们可以看到,此时有一个运行在 59426 端口的 UDP 服务,它是由 dockerd 运行的!或许它就是我们要找的 DNS 服务?

第三步:确定它是不是我们要找的 DNS 服务

我们可以使用 dig 工具来向它发送一个 DNS 查询:

$ sudo nsenter -n -t 1837916 dig +short @127.0.0.11 59426 rails_server
172.18.0.2

奇怪,我们之前运行 dig 的时候,DNS 查询怎么没有发送到 59426 端口,而是发送到了 53 端口呢?这到底是怎么回事呀?

第四步:iptables

对于类似“这个服务似乎正运行在 X 端口上,但我却在 Y 端口上访问到了它,这是什么回事呢?”的问题,我的第一念头都是“一定是 iptables 在作怪”。

于是,我在运行了容器的网络命令空间内执行 iptables-save,果不其然,真相大白:

$ sudo nsenter -n -t 1837916 iptables-save
.... redacted a bunch of output ....
-A DOCKER_POSTROUTING -s 127.0.0.11/32 -p udp -m udp --sport 59426 -j SNAT --to-source :53
COMMIT

在输出中有一条 iptables 规则,它将 53 端口的流量发送到了 59426 上。哈哈,真有意思!

数据库文件储存在一个临时目录中

这样做有一个好处:我可以直接挂载 Postgres 容器的数据目录 ./tmp/db,而无需在我的笔记本电脑上管理 Postgres 环境。

我很喜欢这种方式,因为我真的不想在笔记本电脑上独自管理一个 Postgres 环境(我也真的不知道该如何配置 Postgres)。另外,出于习惯,我更喜欢让开发环境的数据库和代码放在同一个目录下。

仅需一行命令,我就可以访问 Rails 控制台

管理 Ruby 的版本总是有点棘手,并且,即使我暂时搞定了它,我也总是有点担心自己会把 Ruby 环境搞坏,然后就要修它个十年(夸张)。

(使用 Docker Compose)搭建好这个开发环境后,如果我需要访问 Rails 控制台 console (一个交互式环境,加载了所有我的 Rails 代码),我只需要运行一行代码即可:

$ docker-compose exec rails_server rails console
Running via Spring preloader in process 597
Loading development environment (Rails 6.0.3.4)
irb(main):001:0>

好耶!

小问题:Rails 控制台的历史记录丢失了

我碰到了一个问题:Rails 控制台的历史记录丢失了,因为我一直在不断地重启它。

不过,我也找到了一个相当简单的解决方案(嘿嘿):我往容器中添加了一个 /root/.irbrc 文件,它能够把 IRB 历史记录文件的保存位置指向一个不受容器重启影响的地方。只需要一行代码就够啦:

IRB.conf[:HISTORY_FILE] = "/app/tmp/irb_history"

我还是不知道它在生产环境的表现如何

到目前为止,这个项目的生产环境搭建进度,还停留在“我制作了一个 DigitalOcean droplet(LCCT 译注:一种 Linux 虚拟机服务),并手工编辑了很多文件”的阶段。

嗯……我相信以后会在生产环境中使用 docker-compose 来运行一下它的。我猜它能够正常工作,因为这个服务很可能最多只有两个用户在使用,并且,如果我愿意,我可以容忍它在部署过程中有 60 秒的不可用时间。不过话又说回来,出错的往往是我想不到的地方。

推特网友提供了一些在生产中使用 docker-compose 的注意事项:

  • docker-compose up 只会重启那些需要重启的容器,这会让重启速度更快。
  • 有一个 Bash 小脚本 wait-for-it,你可以用它来保持等待一个容器,直到另一个容器的服务可用。
  • 你可以准备两份 docker-compose.yaml 文件:用于开发环境的 docker-compose.yaml 和用于生产环境的 docker-compose-prod.yaml。我想我会在分别为 Nginx 指定不同的端口:开发时使用 8999,生产中使用 80
  • 人们似乎一致认为,如果你的项目是一台计算机上运行的小网站,那么 docker-compose 在生产中不会有问题。
  • 有个人建议说,如果愿意在生产环境搭建复杂那么一丢丢,Docker Swarm 就或许会是更好的选择,不过我还没试过(当然,如果要这么说的话,干嘛不用 Kubernetes 呢?Docker Compose 的意义就是它超级简单,而 Kubernetes 肯定不简单 : ))。

Docker 似乎还有一个特性,它能够 把你用 docker-compose 搭建的环境,自动推送到弹性容器服务(ESC)上,听上去好酷的样子,但是我还没有试过。

docker-compose 会有不适用的场景吗

我听说 docker-compose 在以下场景的表现较差:

  • 当你有很多微服务的时候(还是自己搭建比较好)
  • 当你尝试从一个很大的数据库中导入数据时(就像把几百 G 的数据存到每个人的笔记本电脑里一样)
  • 当你在 Mac 电脑上运行 Docker 时。我听说 Docker 在 macOS 上比在 Linux 上要慢很多(我猜想是因为它需要做额外的虚拟化)。我没有 Mac 电脑,所以我还没有碰到这个问题。

以上就是全部内容啦!

在此之前,我曾花了一整天时间,尝试使用 Puppet 来配置 Vagrant 虚拟机,然后在这个虚拟机里配置开发环境。结果,我发现虚拟机启动起来实在是有点慢啊,还有就是,我也不喜欢编写 Puppet 配置(哈哈,没想到吧)。

幸好,我尝试了 Docker Compose,它真好简单,马上就可以开始工作啦!


via: https://jvns.ca/blog/2021/01/04/docker-compose-is-nice/

作者:Julia Evans 选题:lujun9972 译者:lkxed 校对:turbokernel

本文由 LCTT 原创编译,Linux中国 荣誉推出

ubuntu打开的正确方式(笑)

在这个快节奏的时代。能够使用最快速度最低代价完成任务具有非常重要的意义,对于现代开发者来说,Linux是生活中必不可少且不可替代的工具,如果我们在开发少或学习中少一些遇到环境的坑,会节省很多时间,潜伏在各种Linux群中数年,发现大家都有手贱(操作失误,笑),因此在本文中给出终极解决方案: 直接使用Ubuntu Live CD 来完成系统的启动。

那么最快的Linux安装办法又是什么呢?其实经过严谨的思考之后我个人觉得是不安装。我们可以准备从grub启动ubuntu操作系统把系统放到内存中,再预制出各种情况的环境包,也就是环境模块化,因为系统是在整个内存中所以速度非常非常快,需要模块化的内容并不多,比如说ssh vim tmux 为一组工具,开机后一个命令就可以激活工具包,不到几秒钟的时间即可完成,其他的如Chrome浏览器一类的都可以快速完成在内存上的安装。通过这种方式让环境更加灵活更加快速准备好开发环境。

这是一个思路上的变化,从我需要什么就安装什么一起都准备好给你用->变成了只启动基础的系统然后我需要什么我就后加载什么。

最后的准备在于容器,Docker 以及RKT与RunC的准备,在团队内可以秒级推送运行的环境。

本文依然有它的缺点: 主要体现在两个方面

  1. 如果默认驱动不好使会造成很多麻烦。
  2. 对于内存小的开发机器还是做快照比较好。最少4G内存,我们推荐内存在8G以上最好是16G或者32G内存的机器上采用此方案。

因为本方案整个操作系统都是加载到内存中的,硬盘速度即为内存的速度,我这里实测可以达到4.5Gb/s,所以你准备好屌丝逆袭(买不起SSD)秒杀高富帅了吗?

ubuntu

对于它的简单介绍可以参考这里。当然对于我本人而言选择Ubuntu主要原因仅仅是对他比较熟悉。并不限制使用什么发行版本。如果您要是选本方案搭建环境,并且选择其他发行版本请选择LiveCD尽量小一点的,如果能自己裁剪LiveCD就更好了,另外尽量选择64位架构。当然自己越熟悉越好。

虽然Ubuntu有一点不自由,但是我个人觉得包的质量管理还是非常好的,的确适合开发者使用。

Docker

官方:https://www.docker.com/

它对于我来说,几乎就像是水,如果没有他就没有今天的丰富多彩的运行方式,它可以让程序连同环境一起打包运行,使得部署备份等等非常方便,对于本次环境构建而言我们可以在个人的registry上备份自己的环境,只需要一小段时间就可以克隆回来。再加上现在各种永久免费的国内加速服务这种环境部署就是程序员天堂。更多细节请关注这里

在本文中我们使用单文件版本的Docker文件,方便可控。无任何累赘。本人不喜脚本安装。

RunC && RKT

这两种产品是libcontainer的代表。相对于Docker来说,它是一个容器只需要一个进程,对于Docker来说,他需要一个守护进程。在本文中(本博主)不评价任何产品的好坏,只说什么时候适合用什么。当然在我们这里利用了它启动方便,挂载方便的特性让我们的工作更方便更方便更方便!

由于现在RunC还没有发布1.0版本文档几乎没有,因此我这里会给出我的详细研究结果。至于RKT还是大家自己摸索,文档已经很全了。

CoreOS

作为专门运行容器的操作系统,稳定性可不属性都是前所未有,当然我们的容器可以跑在这个上面本文中会提及两个系统的兼容性方案。

  1. Ubuntu适合程序开发的情况。启动速度可能在1分钟左右(有点慢)。但是有图形界面,有定制基础环境的方案。
  2. CoreOS适合跑程序,启动速度在10秒以下适合释放更多内存来跑临时业务,做测试的时候实用此方案。

从Grub开始

什么是Grub

它可以装在硬盘上帮助你启动操作系统,几乎所有操作系统都可以,当然也包含本次的方案内容。安装方式有很多种。而且必须得根据自己的情况来安装。因为每个人电脑上的操作系统情况都不一样,有的已经有系统了。有的不想给自己装引导。可能先放到u盘上引导都有可能。因此这里不做安装方法上的更多描述可以自己根据情况到搜索引擎上搜索。

Grub 的安装

我之前是安装了windows,所以我直接安装了EasyBCD然后安装NeoGRUB来实现的。具体操作步骤如下:

  1. 下载安装EasyBCD
  2. 如图所示
  3. 安装重启电脑之后选择Grub启动项可以进入一个类似输入命令的界面即可确定完成安装。

Ubuntu 启动配置

启动准备

如果想要准备启动Ubuntu那么有三个关键要素,

  1. ubuntu-14.04.2-desktop-amd64.iso
  2. image中casper/vmlinuz.efi
  3. image中casper/initrd.lz

这三个文件作为启动的必要文件请放到分区中容易找到的地方。推荐做法:给自己的硬盘上开个EXT4 分区,然后把三个文件丢到里面去。

启动参数调试

工作中我们讲求流程,当然我们也需要一定的路程,我们不可能一次调整就成功把ISO启动起来。因此我们先加一个调试流程。

root (hd0,1)
kernel (hd0,1)/casper/vmlinuz.efi boot=casper iso-scan/filename=/ubuntu-14.04.2-desktop-amd64.iso ro quiet splash locate=en_US.UTF-8
initrd (hd0,1)/casper/initrd.lz
  1. 你需要看清楚把文件放到了第几块硬盘上,第几块分区上。组成参数为(hd0,1)(我的是0号硬盘第二个分区)。如果你实在分不清可以参考这里
  2. root (hd0,1) 设置GRUB的root设备
  3. kernel 命令套格式,变化参数 vmlinuz.efi 和 ubuntu-14.04.2-desktop-amd64.iso 要与你放到硬盘上的位置对应,可以使用Tab自动补全非常实用。
  4. initrd 依然是套格式找到对应硬盘傻姑娘的initrd.lz的位置。
  5. 如果出现错误了按会出现刚才的命令,多加修改直到成功启动。

启动参数放到硬盘中

如果是NeoGrub配置文件路径C:\NST\menu.lst,其他的请参考说明文档,一般都会有的。

配置文件写法:

timeout 3
default 0

title Ubuntu
root (hd0,1)
kernel (hd0,1)/casper/vmlinuz.efi boot=casper iso-scan/filename=/ubuntu-14.04.2-desktop-amd64.iso ro quiet splash locate=en_US.UTF-8
initrd (hd0,1)/casper/initrd.lz

3秒等待自动选择第一项,名称ubuntu 别的剩下的就是刚才调试内容的命令了。

完成刚需配置

刚醒需求配置是指在容器之外的刚醒需求配置,比如说编辑器,SSH等等工具箱。配置方法: APT

  1. 修改安装源:sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && apt-get update
  2. 安装你需要的软件包:apt-get install -y vim screen tmux ssh-server等等
  3. 调试你的环境看看是否好使,然后复制文件夹/var/cache/apt/archives中所有deb文件即为您刚才所有的安装包。复制到一个可以持久化的文件夹中等待使用。
  4. 回调环境: 每当您重新启动之后这些安装包即消失,想恢复,进入到上一步中复制到的文件夹中执行如下命令dpkg -i *即可安装预先准备好的软件
  5. 灵活使用,此步骤为发挥组合拳威力的一步。当您反复定制凝练您的安装包之后,会根据个人情况选择不同的目录来初始化您的环境,其结果是,又小又轻,非常可靠。再也不怕手贱导致环境崩坏,再加上外部的git服务保证工作内容的安全性基本上可以肯定的说:我们的环境与硬件与工作耦合度降低不少.
  6. 对于APT无法安装的内容,比如说JDK 与定制版本的Eclipse您可以通过下载完之后持久化保存到硬盘中,写一个脚本解压后放到硬盘中至于环境直接写入到profile中即可。
  7. 使用此方法之后假如您使用IDE真的会有一个非常非常大的速度提升,我打开Eclipse C/C++ 5秒钟即可。

容器安装配置

一般情况下我们安装Docker都是通过网上下载脚本安装,但是这种情况下我们推荐使用直接下载单文件版本的来完成安装安装脚本如下:

curl -sSL -O https://get.daocloud.io/docker/builds/Linux/x86_64/docker-latest
chmod +x docker-latest
mv docker-latest /usr/bin/docker
docker -d & 

在第三行中复制docker程序到任何Path下的文件夹内备用。在配置上,还是要吧/var/lib/docker放到可持久化的软连接下面。比如说按照下面的操作:

rm -rf /var/lib/docker
ln -s /isodevice/docker /var/lib/docekr 

按照这种方法操作可以让docker image 持久化保存在硬盘中。重启也能找回来。这里注意,如果您之前就实践了CoreOS 171.3 版本上的Docker它使用的是1.6版本他的存储文件格式与1.7的不同因此不通用。我就踩到这个坑了。接下来的操作就与Docker一样了。

注意:这个安装包: cgroup-lite_1.9_all.deb 一定要装,默认的光盘中就差这一个依赖,在下面的安装包中有要不就apt安装保存下来都可以。

材料准备

我在百度云盘中准备了三个文件,第一个是docker 1.7.1 第二个准别了vim screen ssh-server tmux CGroup的安装包。最后一个把我常用的Docker images 打包放出来了。其实在准备这个方案花最多时间就是在于精简依赖,最后发现只需要一个CGroup安装包。

总结

在本段文档中,我们给出了一个全新的使用Linux操作系统的思路不安装。并且做了一定的前期准备工作。启动了Linux操作系统做到本步骤如果您是新手就可以开始Linux的学习旅程了,如果您是需要此方案工作已经可以展开一般性质的工作了。
最后我真的拆掉我的三爽垃圾ssd(越用越慢)直接上内存。

方案合理要素:

  1. root权限过大。有时导致无法挽回的误操作。
  2. 现在的电脑内存不是很贵。大部分Linux电脑内存都在闲置。
  3. Linux操作系统如果每次执行的任务如果比较单一(一台物理机只给一个人用)真的不会很大。
  4. 给重新启动计算机赋予了新的意义。
  5. 真正超过SSD的速度,成本更低。(实测启动Eclipse C/C++ 仅需要5s)