高级 Docker 除错

本文翻译于官方论坛

有时,有些东西会不工作。因为 Docker 让您原来习惯的事情变得有点不同,这篇文档将让您知道 Discourse 有什么不同这样您就可以自己除错了。

主机 vs 容器

Discourse 运行在 Docker 容器内,而 Docker 运行在主机上。当您 ssh 登录到您的服务器时,您会看到如下的提示:

kane@newlaptop:~$ ssh root@forum.riking.org
Welcome to Ubuntu 14.04 LTS (GNU/Linux 3.13.0-24-generic x86_64)

 * Documentation:  https://help.ubuntu.com/

  System information as of Fri May 16 11:01:36 PDT 2014

  System load:  0.07               Processes:              139
  Usage of /:   81.2% of 29.40GB   Users logged in:        0
  Memory usage: 47%                IP address for eth0:    107.170.242.76
  Swap usage:   0%                 IP address for docker0: 172.17.42.1


  Graph this data and manage this system at:

    https://landscape.canonical.com/

Last login: Fri May 16 11:01:36 2014 from ***
root@discourse:~# 

这是主机。主机中应该有:

  • /var/discourse 文件夹
  • 安装好的 Docker
  • unattended-upgrades 已经在运行
  • 其他在同样机器的服务

容器是 Discourse 运行的地方。您可以通过使用 ./launcher ssh app 进到容器的 shell 中。当您运行这个命令时,您会看到以下的提示:

root@discourse:/var/discourse# ./launcher ssh app

Welcome to Discourse Docker

Use: rails, rake or discourse to execute commands in production

Last login: Fri May 16 18:01:43 2014 from 172.17.42.1
root@forum:~# 

这是容器。容器中有 /var/www/discourse 文件夹,其中运行着 nginx 和 thin,以及 PostgreSQL 和 Redis(除非您在用多容器安装)。

总之,您在容器中做的改变会在 destoy/bootstrap 后消失。如果您想要对容器做永久性的改变,您需要将其放在容器配置文件中——您的 app.yml 文件。

但是,您在容器中对数据库的操作将在 destory/bootstrap 后继续存在。数据库实际上存在主机上, /var/discourse/shared 目录下。

例子:重建容器

让你们运行以下命令来说明主机和容器的不同。

重建容器其实是一个定期需要的操作。当改变了容器的设置,您就需要重建它。因为我们在对容器执行操作——影响它的生命周期——所以是在主机上完成的。

因为这个操作实在太常见了,所以有一个 ./launcher 命令来帮助完成操作。但是,现在您需要为容器刷新 SSH key,因为它已经过期了:

root@discourse:/var/discourse# git pull
Already up-to-date.
root@discourse:/var/discourse# ./launcher rebuild app
(lots of output....)
root@discourse:/var/discourse# ssh-keygen -f "/root/.ssh/known_hosts" -R [0.0.0.0]:2222

这就是所有的内容了。现在让我们在找一个完整的例子。

例子:主机/容器隔离和共享

这儿是一个演示主机和容器隔离和关系的练习。

在主机运行以下命令。

root@discourse:/var/discourse# ./launcher ssh app
root@forum:~# echo "Some content" > /root/my_file
root@forum:~# echo "Different content" > /shared/shared_file
root@forum:~# cat /root/my_file
Some content
root@forum:~# cat /shared/shared_file
Different content
root@forum:~# logout
logout
root@discourse:/var/discourse# ls shared
backups          postgres_data      redis_data   uploads
log              postgres_data_old  shared_file  vendor_bundle
postgres_backup  postgres_run       ssl
root@discourse:/var/discourse# cat ./shared/shared_file
Different content
root@discourse:/var/discourse# ./launcher rebuild app
-- omitted --
root@discourse:/var/discourse# cat ./shared/shared_file
Different content
root@discourse:/var/discourse# ./launcher ssh app
root@forum:~# cat /root/my_file
cat: /root/my_file: No such file or directory
root@forum:~# cat /shared/shared_file
Different content
root@forum:~# logout
logout
root@discourse:/var/discourse# logout
logout
riking@laptop:~$

注意 /root 中的文件在 rebuild 后被删除了,但是在 shared 文件夹里的文件没有被删除,并且在主机上也可以看到!

例子:删除一个损坏了的数据库

有时您搞坏了数据库,并且需要从备份中重新恢复。但是,如果损坏了的数据库让站点没法稳定运行,您可能没法从 /admin/backups 中上传并恢复老的数据。当灾难发生时,您可能想要彻底删掉数据库。

在主机中:

root@discourse:~# cd /var/discourse
root@discourse:/var/discourse# ./launcher destroy app
root@discourse:/var/discourse# echo rm -rv shared/postgres_*
rm -rv shared/postgres_backup shared/postgres_data shared/postgres_data_old shared/postgres_run
root@discourse:/var/discourse# ./launcher bootstrap app
root@discourse:/var/discourse# ./launcher start app

例子:安装插件

想为 Discourse 安装插件,他们需要被放在 /var/www/discourse/plugins 中。但是,这在容器中——并且会在重建后消失!所以,需要更改容器的配置。

记住,app.yml 是一个 YAML 文件,所以 正确的空格数量及其重要。如果您下载了一个文件编辑后,试着先通过 YAML 验证器测试下文件是否正确再上传改变过的文件。如果您在服务器上直接更改文件,请确定 TAB 键会正确的对齐列。

在 app.yml 文件中已经有了 docker_manager 插件,所以直接将您的插件放在后面!

hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - mkdir -p plugins
          - git clone https://github.com/discourse/docker_manager.git
          - git clone https://github.com/discourse/discourse-spoiler-alert.git
          - git clone https://riking-discourse:insecurepassword@bitbucket.org/riking/some-private-plugin.git

Rails 命令行

Rails 命令行是在容器内运行 rails c 进入的。您可以使用它来查询或者直接修改数据库。警告:玩数据库可能会让您的 Discourse 变得诡异或者以各种方式运行错误。先备份再作死。

我们一样会运行一些命令来展示容器和主机的不同。

例子:查询数据库

让我们找到正好有两个帖子的条目。我们将需要查询数据库,并且这也是用 Rails 能轻松完成的的操作。

让我们进入容器然后获得一个 Rails 命令行:

root@discourse:/var/discourse# ./launcher ssh app
root@forum:~# rails c
2014-05-16T18:23:24Z 303 TID-ovzipu9vw INFO: Sidekiq client with redis options {:url=>"redis://localhost:6379/0", :namespace=>"sidekiq"}
Loading production environment (Rails 4.1.1)
[1] pry(main)> 

我们执行的命令差不多是 Topic.where(???).count。我们需要先知道如何去构造一个 where 查询。让我们先看看 Topic 有的字段:

[2] pry(main)> Topic
=> Topic(id: integer, title: string, last_posted_at: datetime, created_at: datetime, updated_at: datetime, views: integer, posts_count: integer, user_id: integer, last_post_user_id: integer, reply_count: integer, featured_user1_id: integer, featured_user2_id: integer, featured_user3_id: integer, avg_time: integer, deleted_at: datetime, highest_post_number: integer, image_url: string, off_topic_count: integer, like_count: integer, incoming_link_count: integer, bookmark_count: integer, star_count: integer, category_id: integer, visible: boolean, moderator_posts_count: integer, closed: boolean, archived: boolean, bumped_at: datetime, has_summary: boolean, vote_count: integer, archetype: string, featured_user4_id: integer, notify_moderators_count: integer, spam_count: integer, illegal_count: integer, inappropriate_count: integer, pinned_at: datetime, score: float, percent_rank: float, notify_user_count: integer, subtype: string, slug: string, auto_close_at: datetime, auto_close_user_id: integer, auto_close_started_at: datetime, deleted_by_id: integer, participant_count: integer, word_count: integer, excerpt: string, pinned_globally: boolean)

好吧,有好多的东西要看。但是从上到下,从左到右第二行,有一个 posts_count: integer!这好像是我们要的东西。但是,让我们确定一下,看看我们已经知道的主题。要获得一个特定的主题,找一个主题的 URL 看看:

https://forum.riking.org/t/this-is-my-example-topic/24/5

找到第一个数字—— 24,然后运行 Topic.find(24)。我们的主题有 5 个帖子,所以 posts_count 应该要是 5。

[3] pry(main)> Topic.find(24)
=> #<Topic id: 24, title: "This is my example topic", last_posted_at: "2014-05-16 18:28:30", created_at: "2014-04-07 17:16:58", updated_at: "2014-05-16 18:28:32", views: 11, posts_count: 5, user_id: 3, last_post_user_id: 1, reply_count: 2, featured_user1_id: 5, featured_user2_id: nil, featured_user3_id: nil, avg_time: 19, deleted_at: nil, highest_post_number: 5, image_url: nil, off_topic_count: 0, like_count: 0, incoming_link_count: 0, bookmark_count: 0, star_count: 0, category_id: 5, visible: true, moderator_posts_count: 0, closed: false, archived: false, bumped_at: "2014-05-16 18:28:30", has_summary: false, vote_count: 0, archetype: "regular", featured_user4_id: nil, notify_moderators_count: 0, spam_count: 0, illegal_count: 0, inappropriate_count: 0, pinned_at: nil, score: 0.775, percent_rank: 0.419354838709677, notify_user_count: 0, subtype: nil, slug: "this-is-my-example-topic", auto_close_at: nil, auto_close_user_id: nil, auto_close_started_at: nil, deleted_by_id: nil, participant_count: 3, word_count: 29, excerpt: "Foo bar what's up baz", pinned_globally: false>

好了,这就是了!posts_count 确实是 5,是我们想要的结果。让我们运行我们想要的查询吧:

[4] pry(main)> Topic.where(posts_count: 2).count
=> 3

现在我们找到了!有 3 个主题正好有 2 个帖子在里面。搞定。

您可以做一些很复杂的查询并且改变数据库。如果您要中间步骤,将结果赋值给变量,比如手动设置一个管理员账户:

[2] pry(main)> u = User.find_by_username("riking")
=> #<User id: 1, username: "riking", .....snip.......>
[3] pry(main)> u.admin = true
=> true
[4] pry(main)> u.save
=> true
[5] pry(main)> exit

不过,编辑数据库时请小心——可能会把站点变得不稳定,所以先在 /admin/backups 备份 并且下载