学科サイトの静的化の裏側

これは琉大 Advent Calendar 2020の5日目の記事です。

昨日はid:anatofuzさんのvirshの学科ラッパーie-virshをRustで再実装したでした。

まだまだ琉大アドベントカレンダーは空きがあるので皆さんの参加お待ちしております!!!

学科サイトのリプレイス

以前こちらのエントリで書いたように、今までWordPressで動作していた学科サイトを今年のシステム更新に合わせて静的サイト化しました!

anatofuz.hatenablog.com

このエントリはその際のログとなっています

いきなりのhugo化 -> 断念

静的サイト化する場合直接HTMLを編集しないといけなくなるのは避けたいところです。 そのためmarkdownなどからHTMLを生成する静的サイトジェネレーターを使って運用を行いたいと考えました。

システム管理チームの1年生にwebの練習としてhugoテーマを作成してもらおうと思いましたが、難易度が高すぎたようで結局学科システムの更新タイミングまでテーマ作成が間に合いませんでした。 しかしシステム更新後はWordPressを動かしてはいけないというレギュレーションになっていたため、とりあえず現状のWordPressコンテンツをHTMLに変換するという方向で作業を行いました。

wordpressの静的サイト化プラグイン

前回のエントリで紹介したhugo形式のmarkdownWordPressのコンテンツを変換してくれるプラグインですが、学科サイトではプラグインの問題か変換が出来ませんでした。

github.com

似たようなツールにWP2Staticが存在しますが、こちらも件数が多すぎて学科サイトは変換できませんでした。

これらのプラグインは小規模なWordPressサイトでは変換が可能なのですが、学科サイトは様々な問題がありこれらのプラグインが使えなかったのです。

学科サイトのつらみ

学科サイトと読んでいるものは https://ie.u-ryukyu.ac.jpのサイトです。

主に受験生・外部の方向けの https://ie.u-ryukyu.ac.jp/prospective の様なパスと、学科の学生・スタッフ向けの https://ie.u-ryukyu.ac.jp/studentsのパスが大まかなコンテンツパスとなっています。

それだけなら変換は楽そうなのですが、実は学科サイトは https://ie.u-ryukyu.ac.jp/e155730の様に学籍番号をURLにつけると、マルチサイト化された各学生のWordPressページが使われるように設定されています。

同様にnewsサイトはhttps://ie.u-ryukyu.ac.jp/news-ieになっていたり、各講義で使っていたページなども存在するため、ざっと2000件ほどのWordPressサイトが存在しています。

しかもこれらWordPressサイトは、歴史的な理由で数年前からマルチサイト化されており、それ以前はすべて同じWordPressサイトとして運用されていました。そのため、学科トップページを静的化しようとすると、これらのほかユーザーなどのURLもプラグインが同時に変換しに行くため、変換プラグインの負荷がかかり、基本的に変換できないという状況になっていました。

気合で静的サイト化する

プラグインが使えないなら気合でどうにかするしかありません。幸いWP2Staticはページを生成するところまではしませんでしたが、ページのURLをクロールして発見してくれました。

発見してくれたパスの一覧はこんな感じです。

Pending   /robots.txt   Note: initial_crawl_list
Pending   /sitemap/   Note: initial_crawl_list
Pending   /sitemap.xml   Note: initial_crawl_list
Pending   /students/   Note: initial_crawl_list
Pending   /students/get-a-job/   Note: initial_crawl_list
Pending   /students/graduate/   Note: initial_crawl_list
Pending   /students/graduate/%e5%a4%a7%e5%ad%a6%e9%99%a2%e4%bf%ae%e5%ad%a6%e3%81%ae%e6%89%8b%e5%bc%95%e3%81%8d/   Note: initial_crawl_list
Pending   /students/graduate/%e5%a4%a7%e5%ad%a6%e9%99%a2%e5%85%a5%e8%a9%a6%e3%82%a2%e3%83%89%e3%83%9f%e3%83%83%e3%82%b7%e3%83%a7%e3%83%b3%e3%83%9d%e3%83%aa%e3%82%b7%e3%83%bc/   Note: initial_crawl_list
Pending   /students/graduate/%e5%a4%a7%e5%ad%a6%e9%99%a2%e7%a0%94%e7%a9%b6%e8%a8%88%e7%94%bb%e6%9b%b8/   Note: initial_crawl_list
Pending   /students/graduate-students/   Note: discovered on: /
Pending   /students/graduate-students2019/   Note: initial_crawl_list
Pending   /students/graduation-study/   Note: initial_crawl_list
Pending   /students/info-ethics-video/   Note: initial_crawl_list
Pending   /students/install/   Note: initial_crawl_list
Pending   /students/install/event-calendar/   Note: initial_crawl_list
Pending   /students/install/forward-email/   Note: initial_crawl_list
Pending   /students/install/homebrew/   Note: initial_crawl_list
Pending   /students/install/ip-address/   Note: initial_crawl_list
Pending   /students/install/mail/   Note: initial_crawl_list
Pending   /students/install/mercurial/   Note: initial_crawl_list
Pending   /students/install/newsie/   Note: initial_crawl_list
Pending   /students/install/printer/   Note: initial_crawl_list
Pending   /students/install/rss-2/   Note: initial_crawl_list
Pending   /students/install/server-access/   Note: initial_crawl_list
Pending   /students/install/web-server/   Note: initial_crawl_list
Pending   /students/install/wifi/   Note: initial_crawl_list
Pending   /students/office-hour/   Note: initial_crawl_list
Pending   /students/rental/   Note: initial_crawl_list
Pending   /students/request/   Note: initial_crawl_list
Pending   /students/request/guest-id/   Note: initial_crawl_list
Pending   /students/request/guest-wifi/   Note: initial_crawl_list
Pending   /students/request/vm/   Note: initial_crawl_list
Pending   /students/security-policy/   Note: initial_crawl_list
Pending   /students/software/   Note: initial_crawl_list
Pending   /students/study-guide/   Note: initial_crawl_list
Pending   /students/study-teaching-licence/   Note: initial_crawl_list
Pending   /students/study-teaching-licence/risyuu/   Note: initial_crawl_list
Pending   /students/study-teaching-licence/risyuu/%e6%95%99%e5%93%a1%e5%85%8d%e8%a8%b1%e3%82%b3%e3%83%bc%e3%82%b9/   Note: initial_crawl_list
Pending   /students/study-teaching-licence/risyuu/%e7%9f%a5%e8%83%bd%e6%83%85%e5%a0%b1%e3%82%b3%e3%83%bc%e3%82%b9%e5%b1%a5%e4%bf%ae%e8%a6%8f%e5%ae%9a/   Note: initial_crawl_list
Pending   /students/study-teaching-licence/risyuu/%e7%9f%a5%e8%83%bd%e6%83%85%e5%a0%b1%e3%82%b3%e3%83%bc%e3%82%b9%e5%b1%a5%e4%bf%ae%e8%a8%88%e7%94%bb%e8%a1%a8/   Note: initial_crawl_list
Pending   /students/study-teaching-licence/risyuu/2017%e5%b9%b4%e5%ba%a6-%e5%be%8c%e6%9c%9f/   Note: initial_crawl_list
Pending   /students/study-teaching-licence/shinro/   Note: initial_crawl_list
Pending   /students/study-teaching-licence/shinro/%e3%82%a4%e3%83%b3%e3%82%bf%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%83%e3%83%97/   Note: initial_crawl_list
Pending   /students/timetable/   Note: initial_crawl_list
Pending   /students/timetable/2018-latter/   Note: initial_crawl_list
Pending   /students/timetable/2018-previous/   Note: initial_crawl_list
Pending   /students/timetable/2019-latter/   Note: initial_crawl_list
Pending   /students/timetable/2019-previous/   Note: initial_crawl_list
Pending   /students/timetable/2020-previous/   Note: initial_crawl_list
Pending   /survey-ob/   Note: discovered on: /%e5%ad%a6%e5%86%85%e5%90%91%e3%81%91/
Pending   /tag/%e3%82%af%e3%83%a9%e3%82%b9%e3%82%bf/   Note: initial_crawl_list
Pending   /tag/%e3%83%a1%e3%83%bc%e3%83%ab/   Note: initial_crawl_list
Pending   /tag/%e3%83%aa%e3%83%b3%e3%82%af%e9%9b%86/   Note: initial_crawl_list
Pending   /tag/%e3%83%ad%e3%82%b0%e3%82%a4%e3%83%b3%e3%82%b7%e3%82%a7%e3%83%ab/   Note: initial_crawl_list
Pending   /tag/%e4%bb%ae%e6%83%b3os/   Note: initial_crawl_list
Pending   /tag/%e5%89%b2%e3%82%8a%e5%bd%93%e3%81%a6/   Note: initial_crawl_list
Pending   /tag/%e6%89%80%e5%b1%9evlan/   Note: initial_crawl_list
Pending   /tag/bashou/   Note: initial_crawl_list
Pending   /tag/editor/   Note: initial_crawl_list
Pending   /tag/emacs/   Note: initial_crawl_list
Pending   /tag/imac/   Note: initial_crawl_list
Pending   /tag/mac/   Note: initial_crawl_list
Pending   /tag/procmailrc/   Note: initial_crawl_list
Pending   /tag/spam/   Note: initial_crawl_list
Pending   /tag/spamassassin/   Note: initial_crawl_list
Pending   /tag/tcshrc/   Note: initial_crawl_list
Pending   /tag/torque/   Note: initial_crawl_list
Pending   /tag/vi/   Note: initial_crawl_list
Pending   /tag/xen/   Note: initial_crawl_list
Pending   /wp-admin/   Note: discovered on: /
Pending   /wp-content/plugins/contact-form-7/includes/js/scripts.js   Note: discovered on: /
Pending   /wp-content/plugins/wc-shortcodes/public/assets/css/font-awesome.min.css   Note: discovered on: /
Pending   /wp-content/plugins/wc-shortcodes/public/assets/css/style.css   Note: discovered on: /
Pending   /wp-content/plugins/wc-shortcodes/public/assets/js/rsvp.js   Note: discovered on: /
Pending   /wp-content/plugins/wp-browsing-history-master/jquery.cookie.js   Note: discovered on: /
Pending   /wp-content/plugins/wp-browsing-history-master/wp-browsing-history-display.js   Note: discovered on: /
Pending   /wp-content/plugins/wp-browsing-history-master/wp-browsing-history-logging.js   Note: discovered on: /
Pending   /wp-content/plugins/wp-browsing-history-master/wp-browsing-history-variables.js   Note: discovered on: /
Pending   /wp-content/plugins/wp-browsing-history-master/wp-browsing-history.css   Note: discovered on: /
Pending   /wp-content/plugins/wp-rss-aggregator/css/colorbox.css   Note: discovered on: /
Pending   /wp-content/plugins/wp-rss-aggregator/css/styles.css   Note: discovered on: /
Pending   /wp-content/plugins/wp-rss-aggregator/js/custom.js   Note: discovered on: /
Pending   /wp-content/plugins/wp-rss-aggregator/js/jquery.colorbox-min.js   Note: discovered on: /
Pending   /wp-content/plugins/wp-to-twitter/css/twitter-feed.css   Note: discovered on: /
Pending   /wp-content/themes/education-booster/assets/js/main.js   Note: discovered on: /
Pending   /wp-content/themes/education-booster/assets/vendors/bootstrap/css/bootstrap.min.css   Note: discovered on: /
Pending   /wp-content/themes/education-booster/assets/vendors/bootstrap/js/bootstrap.min.js   Note: discovered on: /
Pending   /wp-content/themes/education-booster/assets/vendors/kf-icons/css/style.css   Note: discovered on: /
Pending   /wp-content/themes/education-booster/assets/vendors/OwlCarousel2-2.2.1/assets/owl.carousel.min.css   Note: discovered on: /
Pending   /wp-content/themes/education-booster/assets/vendors/OwlCarousel2-2.2.1/assets/owl.theme.default.min.css   Note: discovered on: /
Pending   /wp-content/themes/education-booster/assets/vendors/OwlCarousel2-2.2.1/owl.carousel.min.js   Note: discovered on: /
Pending   /wp-content/themes/education-booster/style.css   Note: discovered on: /
Pending   /wp-content/themes/education-booster-child/style.css   Note: discovered on: /
Pending   /wp-includes/js/imagesloaded.min.js   Note: discovered on: /
Pending   /wp-includes/js/jquery/jquery-migrate.min.js   Note: discovered on: /
Pending   /wp-includes/js/jquery/jquery.js   Note: discovered on: /
Pending   /wp-includes/js/jquery/jquery.masonry.min.js   Note: discovered on: /
Pending   /wp-includes/js/masonry.min.js   Note: discovered on: /
Pending   /wp-json/   Note: discovered on: /
Pending   /wp-json/oembed/1.0/embed   Note: discovered on: /

なんかデバッグ情報が付与されているので、perlスクリプトを書いて解決しました。 Perlだと__DATA__以下に書いたテキストをwhile (my $line = <DATA>)の用にして取得できるので便利です。

github.com

この結果こんな感じのパスが生成されました

students/install/newsie
students/install/printer
students/install/rss-2
students/install/server-access
students/install/web-server
students/install/wifi
students/office-hour
students/rental
students/request
students/request/guest-id
students/request/guest-wifi
students/request/vm
students/security-policy
students/software
students/study-guide
students/study-teaching-licence
students/study-teaching-licence/risyuu
students/study-teaching-licence/risyuu/%e6%95%99%e5%93%a1%e5%85%8d%e8%a8%b1%e3%82%b3%e3%83%bc%e3%82%b9
students/study-teaching-licence/risyuu/%e7%9f%a5%e8%83%bd%e6%83%85%e5%a0%b1%e3%82%b3%e3%83%bc%e3%82%b9%e5%b1%a5%e4%bf%ae%e8%a6%8f%e5%ae%9a
students/study-teaching-licence/risyuu/%e7%9f%a5%e8%83%bd%e6%83%85%e5%a0%b1%e3%82%b3%e3%83%bc%e3%82%b9%e5%b1%a5%e4%bf%ae%e8%a8%88%e7%94%bb%e8%a1%a8
students/study-teaching-licence/risyuu/2017%e5%b9%b4%e5%ba%a6-%e5%be%8c%e6%9c%9f
students/study-teaching-licence/shinro
students/study-teaching-licence/shinro/%e3%82%a4%e3%83%b3%e3%82%bf%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%83%e3%83%97
students/timetable
students/timetable/2018-latter
students/timetable/2018-previous
students/timetable/2019-latter
students/timetable/2019-previous
students/timetable/2020-previous
survey-ob
tag/%e3%82%af%e3%83%a9%e3%82%b9%e3%82%bf
tag/%e3%83%a1%e3%83%bc%e3%83%ab
tag/%e3%83%aa%e3%83%b3%e3%82%af%e9%9b%86

このパスをもとにひたすらwget的なことをして、HTMLをダウンロードしてくれば人まずは静的化できそうです。

このURL一覧に画像やPDFファイルなどは含まれていませんが、それはこのURLから落としてきたHTMLをパースして発見すれば良いでしょう。

ダウンローダーの実装

なんとなくgoroutineを使って解決したかったのでgolangを選択しました。

github.com

脳死でgoroutineを書くとファイルディスクリプタを食い尽くして死ぬので、CPUのコア数あたりを見ていい感じにgoroutineの数を抑制しています。

   wg := &sync.WaitGroup{}
    cpus := runtime.NumCPU()
    semaphore := make(chan bool, cpus)
    ctx := context.Background()

基本的には先程のパスを与えてあげるとダウンロード出来るように仕込んでいます。

HTMLからcssや画像ファイル, javascriptを回収するのは2段階で行っています。

(なんでこんなにクソ面倒なことをしているかというと、生成するファイルパスをgolangURIから変更するのが面倒だったためです...)

#!/usr/bin/env perl
use strict;
use warnings;
use JSON::PP qw/encode_json/;
use utf8;

use File::Find qw/find/;

find({wanted => \&wanted, no_chdir => 1}, 'ie.u-ryukyu.ac.jp');

my $urls;
use DDP {deparse => 1};

sub wanted {
  my $filename = $_;
  if ($filename !~ /\.html/) {
    return;
  }

  open my $fh, '<:utf8', $filename;
  while (my $line = <$fh>) {
    if ($line =~ m|text/javascript.*src=(['"])(https.*?)\1|) {
        unless (exists $urls->{js}->{$2}) {
          $urls->{js}->{$2} =1;
        }
        next;
    }
    #  if ($line =~ /stylesheet.*href=(['"])(http.*?)\1/ ) {
    #    unless (exists $urls->{css}->{$2}) {
    #      $urls->{css}->{$2} =1;
    #    }
    #    next;
    #  }
    #  if ($line =~ /img.*src=(['"])(http.*?)\1/) {
    #    unless (exists $urls->{img}->{$2}) {
    #      $urls->{img}->{$2} =1;
    #    }
    #    next;
    #  }
    #  #<a href="https://ie.u-ryukyu.ac.jp/files/2015/04/%E5%B7%A5%E5%AD%A6%E9%83%A8%E5%AE%89%E5%85%A8%E8%A1%9B%E7%94%9F%E3%83%9E%E3%83%8B%E3%83%A5%E3%82%A2%E3%83%AB.pdf" target="_blank" rel="noopener">工学部 安全衛生マニュアル(PDF)</a>
    #  while ($line =~ m|href=(['"])(https?://.*/files/.*?)\1|g) {
    #    unless (exists $urls->{files}->{$2}) {
    #      $urls->{files}->{$2} =1;
    #    }
    #    next;
    #  }
  }
}


my $outputJsonData = {};

for my $key (qw/js/) {
  for my $url (sort keys %{$urls->{$key}}) {
    my $filePATH = $url;
    $filePATH =~ s|https?://||;

    my $outputDir = $filePATH;
    $outputDir =~ s|(.+)/(.+)$|$1|;
    push(@{$outputJsonData->{$key}}, {url => $url, filePATH => $filePATH, outputDir => $outputDir});
  }
}

#$outputJsonData->{css} = [ sort ];
#$outputJsonData->{img} = [ sort keys %{$urls->{img}}];
#
print encode_json($outputJsonData);

パスの相対パス

取得したこれらのコンテンツですが、WordPressは仕様でリンクをすべて絶対URLで作成するので、ローカルでCSSや画像のテストが出来ません。

今回はPerlスクリプトを用いてs|https://ie.u-ryukyu.ac.jp||gのようなことを行い、相対パスに変換しています。こうすると、変換したあとはlocalでwebサーバーを立てて確認する事が可能となります。

テスト用のwebサーバーは当初psgiで実装しましたが、他の人が使いそうなのでgolangで書き直しました。

use Plack::Builder;

builder {
    enable "Plack::Middleware::Static",
    path => sub {
        s!/([^\.]+)$!/$1/!;
        s!(.*/$)!$1/index.html! or return qr{^/.+};
    },
    content_type => sub {
        my $file = shift;
        my $content_type = Plack::MIME->mime_type($file) || 'text/plain';

        if ($content_type eq 'application/atom+xml') {
            $content_type .= "; charset=utf-8";
        }

        return $content_type;
    },
    root => './ie.u-ryukyu.ac.jp/';
};

github.com

ということで学科サイトはひとまず静的化が完了しました。

ie.u-ryukyu.ac.jp

静的化したため、学科のGitLabや、GitHubにgitリポジトリ化したサイトをcloneするだけでバックアップになるので、極めて便利です。さらにwebサーバーをapacheからnginxに乗り換えたため、スピードが大分向上しました。

早くなった学科サイトですが、いくつか課題があります

  • markdownから編集しやすくしたい
  • wordpress自体のCSSとかが一部あたっている
  • 更新フローの確立

このあたりもシステム管理チームで行っているので、webやってみたい人がいたらぜひシス管に入りましょう!!!!

明日はYoshiaki Sanoさんで「 ニューラルネットワークの仕組みと実装の解説」です。お楽しみに!!

virshの学科ラッパーie-virshをRustで再実装した

これは琉大 Advent Calendar 2020の5日目の記事です。

昨日は未承諾広告※ shangliさんのクラスター計算機を作るときに気をつけたいことでした。

まだまだ琉大アドベントカレンダーは空きがあるので皆さんの参加お待ちしております!!!

学科システム

さて僕たちの学科(琉大情報工・知能情報コース・情報工学専攻)では独自にオンプレ上に構築した学科システムを持っています。学科システムのサーバーの管理はシステム管理チームと呼ばれる学生たちが、自分達自身で行うという方向性です。

現在の学科システムはubuntuベースのサーバーが4台、ファイルサーバーが2台、さくらの専用サーバーが1台という構成になっていて、ubuntuベースの演習用サーバー2つは学生に広く開放しています。

この学科システムでは2015年まではVMWare, 2015年以降はKVMを運用しており、学科サービスや学生演習用・学生が個人で開発するようでVMを動作させることが可能となっています。

KVMの学生ごとの管理

例えばOSの講義では学生がそれぞれVirtualBoxで作成したFedoraVMイメージを、学科システムにrsyncし、KVMとして動作させる課題が存在します。

学生にはそれぞれKVMの操作コマンドであるvirshの操作権限は与えていますが、いくつか問題点がありました。

  • virsh defineXMLを書くのがめんどい
  • virsh installが複雑
  • 人のVMを間違って消してしまう可能性がある
  • VNCパスワードを生成するのがめんどう
  • qcow2の置き場所は作って欲しい

これらを解決するために、以前の学科システムではie-virshというコマンドが存在していました。 これはvirshコマンドのラッパーで、以下の事が可能なコマンドです。

  • ie-virsh define 01でテンプレートXMLをもとにVMを構成
  • qcow2の置き場所も生成
  • ie-virsh start 01username-01というVMを起動できる
  • ie-virsh listvirsh list --all | grep $usernameと同じ挙動をする
  • ie-virsh destroy 01VMを終了できる
  • ie-virsh dumpxml 01XMLを確認できる

以前のシステムではこのie-virshを利用することで、5個のVMを作成することができ、かつ起動することが出来ていました。このラッパーは他のユーザーのVMは操作できないので安全でもあります。

貸し出しVMサービス

さらにシステム管理チームでは、あらかじめユーザー設定などをすませたVMのテンプレートイメージをもとに、ユーザーの希望に応じてVMを作成する貸し出しVMサービスがありました。

今まではRubyonRailsで実装されたAkatsukiというweb アプリケーション経由で行っていました。

attonblog.blogspot.com

(なおAkatsukiはシステム移行に伴って, Apache + Passengerから Nginx + Dockerで rails sに切り替えています)

Akatsukiでだいぶ問題はなかったのですが、VM操作にfog-libvirtを使っているため、テンプレートVMからのコピー時に、VMの差分イメージではなくVMイメージのフルコピーが使われてしまいます。

そのため差分イメージ(backing file)を使うにはRailsアプリケーション側の大規模な修正が必要そうでした。残念ながら現状のシス管メンバーはそこまでRailsの戦力に強いメンバーが揃っているわけではないので、CLIとして実装したいという気持ちになっていました。

ie-virshの再実装

そこでAkatsukiのVM作成的な機能をie-virshに組み込むぞ!!という気持ちになります。

今まで動いていたie-virshの実装はC + Python2という構成であり、python2を現代のサーバーで動かしたくはないので、python3へのリライトが迫られていました。

ie-virshのリポジトリ

上記のAkatsukiの機能を c + python3で組み込むのはなかなか手がかかりそうですし、せっかくなので別言語で書き直そうという機運がid:anatofuzの中で高まります。

そこでCに近い言語として興味があったRustを選択し、実装を行いました。

このあたりは作業の副産物です。 anatofuz.hatenablog.com

anatofuz.hatenablog.com

Rustで書いたie-virsh

実際に実装した結果がこれです

gitlab.ie.u-ryukyu.ac.jp

使い勝手

従来のシステムはヘルプがなかったですが、なんとhelp付きです

$ie-virsh
ie-virsh 0.1.2
AnaTofuZ <anatofuz@cr.ie.u-ryukyu.ac.jp>

USAGE:
    ie-virsh <SUBCOMMAND>

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

SUBCOMMANDS:
    console       connect to the guest console
    define        define (but don't start) a domain from an template XML file
    define-gdb    define the domain in which the gdb port is opened from the template XML file
    destroy       destroy (stop) a domain
    domiflist     list all domain virtual interfaces
    dominfo       domain information
    dumpxml       domain information in XML
    edit          edit XML configuration for a domain
    help          Prints this message or the help of the given subcommand(s)
    list          list domains
    shutdown      gracefully shutdown a domain
    start         start a (previously defined) inactive domain
    templates     show templates vm
    ttyconsole    tty console
    undefine      undefine a domain
    vncdisplay    vncdisplay

かなりのコマンドをサポートしており、今まではdestroyしかできませんでしたが、shutdownも出来るようになってます。

list

自分が作ったVMの状況が確認可能です

$ie-virsh list
uid: 11464 gid: 1001 name: k198584
 Id   Name                            State
------------------------------------------------
 -    k198584(anatofu-prome.cr)       shut off
 -    k198584-centos                  shut off
 -    k198584-centos2                 shut off
 -    k198584-u20                     shut off
 -    k198584-ubuntu18                shut off

define

  • テンプレートxmlをもとにvmをdefineします
    • 名前はなんでも良いですが、prefixにlogin user nameが入る仕様です
    • 例えば e155730 が ie-virsh define anatofuz とすると e155730-anatofuzというVMが作られます

start, dumpxmlなど

  • vm名を指定する必要がありますが、wrapperなので全部打つ必要がないようにしています
    • e155730-anatofuz の場合は ie-virsh start anatofuz でOKです
  • vm名をフルで打っても問題ないです
  • 起動しているvmに対しての操作はidを指定しても問題ないようになっています

テンプレートVMをもとに差分で生成する

ie-virsh templatesとすると、対応しているテンプレート一覧が出ます

$ie-virsh templates
uid: 11464 gid: 1001 name: k198584
CentOS-7
CentOS-8
Debian-10
Debian-8
Fedore-25
Ubuntu-16
Ubuntu-18
Ubuntu-20

そしてこれをdefineのときに-tオプションで指定すると、VMが差分で生成されます!

+amane+k198584 ie-virsh define dondoko -t Ubuntu-20
uid: 11464 gid: 1001 name: k198584
k198584-dondoko
generate xml : /etc/libvirt/qemu/k19/k198584/k198584-dondoko.xml
vnc password : DQCyWV2YQ$w(!B#(M0#8jcWO%hnqOj
Formatting 'k198584-dondoko.qcow2', fmt=qcow2 size=10737418240 backing_file=/ie-ryukyu/kvm/images/templates/template-Ubuntu-20.qcow2 backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
Domain k198584-dondoko defined from /etc/libvirt/qemu/k19/k198584/k198584-dondoko.xml

実際に確認をしてみます

$ie-virsh list
uid: 11464 gid: 1001 name: k198584
 Id   Name                            State
------------------------------------------------
 -    k198584(anatofu-prome.cr)       shut off
 -    k198584-centos                  shut off
 -    k198584-centos2                 shut off
 -    k198584-dondoko                 shut off
 -    k198584-u20                     shut off
 -    k198584-ubuntu18                shut off

無事作成されていますね!!!(ちなみにこのあと学科内では、VMDHCP対象にするためにmacアドレスを登録する必要があります...)

実装について

Rustで最初に書いた本格的なプログラムなので、かなり汚いと思いますが、練習になりましたし何より書いてて楽しかったですね。

CLIの実装はclapを使っています。 なんとなくenumでサブコマンドをパターンマッチしたかったのですが、deriveを使う以外のやり方が解らず、無駄に構造体を量産してしまっています。やり方教えてほしい。

execやseuidのようなUNIXAPIを叩く必要があり、これにはnixが便利でした。とはいえnixはlibcのラッパーっぽいので、はじめからlibcを叩けばよかったのかもしれません。

実装したいはvscodeのremote edit機能を使って、サーバーに直接sshして書いていました。もともとローカルなmacOSでやっていましたが、linux用のバイナリを吐くためにdockerを起動するのがしんどくなってしまい、直接linuxのバイナリを吐きつつ編集できる方法が欲しかったので、vscode最高!!!みたいになっていました。

というわけで新しいie-virshの紹介でした。みんな使ってみてください。あとプルリクお待ちしております。

明日はxxxxさんです。

rustコンパイラのデバッグではrust-lldbはだめ

今日明日やっている河野先生のコンパイラ読み会で今年はRustを読んでいるけれど、rust-lldbはどうもrust compilerレベルの巨大なプロジェクトのデバッグは微妙っぽい。

どうも見たら公式がNone of the LLDB work is upstream. This rust-lang/lldb wiki page explains a few details.とか言っている。おいおい。。。LLVM backendなので逆にlldbの方使ってそうなのに。

rustc-dev-guide.rust-lang.org

関数名でbreakpointを貼るいつものオペレーションも、pubなものは

b rustc_interface::passes::parse

みたいにbreakpointを貼れるけど、例えばimplしている中で定義している関数の場合は

(gdb) b parse_fn_
rustc_parse::parser::expr::<impl rustc_parse::parser::Parser>::parse_fn_block_param
rustc_parse::parser::item::<impl rustc_parse::parser::Parser>::parse_fn_decl
rustc_parse::parser::item::<impl rustc_parse::parser::Parser>::parse_fn_front_matter
rustc_parse::parser::item::<impl rustc_parse::parser::Parser>::parse_fn_params::{{closure}}
(gdb) b parse_fn_body

の様に <>にimplの型名をいれないと捕獲できず、さらにpubでないものはbreak pointを上手く貼れないっぽい。

一応回避策として、例えばrustc_parseのparser::iterm::parse_fn_bodyにbreak pointを貼るときはb item.rs:1535の様に直接行番号を指定すればいいっぽい。ただこれも止まらないケースがあるのでわりかし微妙っぽい。

ログ

scrapbox.io

growi.cr.ie.u-ryukyu.ac.jp

Growiで使っているmongodbのupgrade

普段運用しているgrowiがバージョン4.2.0がリリースされ、mongodb 4.4をサポートする様になったのでアップグレードしてみた。

github.com

アップグレードに関してはドキュメントがあるのでドキュメントの通りにやっていくのが良い。

docs.growi.org

推奨されているdocker-composeで運用している場合、mongodbのバージョンはdocker-compose.ymlでmongodbのdocker imageのバージョンとして指定されている。

  mongo:
    image: mongo:3.6
    restart: unless-stopped
    volumes:
      - mongo_configdb:/data/configdb
      - mongo_db:/data/db
    restart: always

そのためアップグレードする場合は、このmongo:に続く数字を変えた上で、docker-compose stopdocker-compose upをすればいい感じ。簡単ですね。

mongodb公式によると、コンテナが立ち上がったらmongoシェルを起動して多少操作する必要があるらしい。まぁこれも簡単。

docs.mongodb.com

基本的にdocker-compose exec mongo mongodでmongoシェルを起動し、upgradeしたバージョンをdb.adminCommand( { setFeatureCompatibilityVersion: "4.4" } )の様に設定する必要がある。

$docker-compose exec mongo mongod
$db.adminCommand( { setFeatureCompatibilityVersion: "4.4" } )

最初はいけるやろ感があったので、唐突に今動かしている3.6から4.2にupgradeしたところmongodbが立ち上がらなくなった。 調べたところGrowi公式の方も認識していて、mongodbのupgradeはドキュメントに書いてあるとおり段階的にupgradeする必要があるらしい。

dev.growi.org

ということでgrowi公式にある通り

  • MongoDB v3.6 から v4.0 へのアップグレード
  • MongoDB v4.0 から v4.2 へのアップグレード
  • MongoDB v4.2 から v4.4 へのアップグレード

このあたりを順にしてったところ無事upgradeできた。データ消えなくて良かったですね。

singularityを使ったRustコンパイラのデバッグビルド

学科システムにはsingularityというコンテナサービスを導入している。動かしているpodmanと比較するとこのあたりが便利と思っています。

  • ceph fs上でも結構高速に動作
  • root権限がいらない
    • podmanもrootlessではあるが、ceph FS上だと遅い...
  • singularity shellで気軽にコンテナのシェルを立ち上げられる
  • 科学計算向きなのでスパコンなどでの利用実績がある
  • ホームディレクトリなどがattachしたタイミングで自動的にコンテナにマウントされる

とはいえ良い事ばかりではなくツラミポイントもあります。 例えばDockerを使っている場合は構成をDockerfileで書きたくなりますが、singularityで同様のビルドファイルを作ろうとした場合、各コマンドのキャッシュが効きません。

dockerの場合は行単位でキャッシュが効いていたのですが、singularity build的な感じでファイルからビルドしようとすると、失敗した場合最初からになるため、本筋ではないapt updateなどで莫大な時間を浪費します。

しかしsingularityはdockerと違い、--sandboxで実行するといかにもchrootlinux namespaceみがあるディレクトリを作成し、この上で様々な構築をインタラクティブにしたあとにシングルバイナリのsifに固めるという技があるので、dockerファイルの様なファイル形式でなく、インタラクティブに構築する方法が適していそうです。

今回はRustコンパイラデバッグ環境をsingularityで作るのを通して、インタラクティブな構築方法を見てみます。

インタラクティブに構築する場合は、--sandbox--writable を使ってビルド及び環境構築を行います。

sandbox環境の作成

とりあえずaptのupdateだけするファイルrust-debug.defを生成します。

 BootStrap: docker
 From: ubuntu:20.04
 
 %post
     apt-get -y update
     apt-get -y upgrade

sandbox環境のビルド

$singularity build --sandbox --fakeroot rust-debug rust-debug.def

  • sifではなくディレクトリが生成されます
    • ディレクトリの中身はlinux namespaceで区切られていそうな気配ですが、コンテナの中の/移行が置かれています

sandbox環境にシェルログインして環境を作る

$singularity shell --fakeroot --writable rust-debug

こうするとシェルログインが出来ます。 --fakerootはホストOS側でroot権限がないユーザーでも、コンテナの中でrootとして振る舞う事ができるオプションです。

Rustコンパイラデバッグ環境の構築

さてシェルログインできたら、Rustcompilerのデバッグビルドに必要そうなものをインストールします。 なにが必要かはGitHubリポジトリに書いてあるので読んでみます。

github.com

Singularity> apt install -y cmake python3 clang ninja-build lldb gdb curl git

とりあえず必要そうなものをinstall

  • cmake
  • python3
  • clang
  • ninja-build
    • ninjaはaptだとこの名前
  • lldb
  • gdb
  • curl
  • git

無事にインストールされたら、singularityコンテナの中でpythonと入力するとpython3が起動するように仕込みます。

update-alternatives --install /usr/bin/python python /usr/bin/python3 10

参考 Unable to set default python version to python3 in ubuntu

/rootにRustのリポジトリを起きたいところですが、ここは実行ユーザーの$HOMEとマウントされるので適当なディレクトリをつくります

  • $mkdir /rust
  • $cd /rust

Rustコンパイラデバッグビルド

rustのリポジトリをcloneします - $git clone https://github.com/rust-lang/rust.git

Rustはビルドの設定をTOMLで記述します。exampleをもとにするのが良いらしいので、copyします。

  • $cp config.toml.example config.toml

aptでvimをinstallし、設定を書き換えていきます。 まずdebugビルドするので、デバッグオプションを有効化します。 ビルド時にninjaを使いたいのでninjaを有効にし、趣味でinstall先のprefixを変えておきます。

まとめると次のような変更をします。

  • #debug = falsedebug = true
  • #ninja = falseninja = true
  • #prefix = "/usr/local"prefix = "/usr/local"

Rustのビルドとインストール

RustはビルドスクリプトPythonで書かれたx.pyですので、これを実行します

  • ./x.py build && ./x.py install

今回は/rust/rustリポジトリで、ここでビルドを実行しました。 そのため生成されたRustのバイナリは/rust/rust/build/x86_64-unknown-linux-gnu/以下に配置されています。

例えばLLDBでデバッグする場合はrust-lldb /rust/rust/build/x86_64-unknown-linux-gnu/stage1/bin/rustcとするとデバッグ可能です。

ビルドした結果をsifに変換する

sandbox環境で無事ビルドできたので、これを配布可能なsif形式に固めます。

固めるときはbuildで、defファイルでなくディレクトリを指定します。

  • $singularity build --fakeroot debugging-rust-compiler.sif rust-debug

この結果、debugging-rust-compiler.sifが生成されており、以降はこれを使います。

設定したsifに接続する

作成したsifにシェルログインしてみます

$singularity shell debugging-rust-compiler.sif

あわせてよみたい

singularityを使ったRustのデバッグビルド - cr-ryukyu

singularityでRustコンパイラのデバッグ - cr-ryukyu

Perlでgrowiのバックアップファイルからmarkdownを生成する(version1くらい)

研究室ではwikiとしてgrowiを使っています。

growi.cr.ie.u-ryukyu.ac.jp

まぁ便利なのですが、growiはデータベースがmongoDBなので、心もとないという意見や、せっかく階層構造を持っているのでmarkdown単体で保存しておきたいという意見が出ています。

もともとAPI経由でやろうとしていましたが、まぁせっかくなのでbackupファイルからmarkdownを生成してみます。

記事のbackupファイルの取得

growiでは記事はrevisionsというコレクションで登録されています。 growiのwebページからbackupを作成するか、dockerで動かしている場合は次のようなコマンドでrevisonsのjsonを取得します。

$docker exec growi_mongo_1 mongoexport -d growi -c revisions --out revision_back_1013.json
$docker  cp growi_mongo_1:revision_back_1013.json .
$docker exec growi_mongo_1 rm revision_back_1013.json

ここで取得したjsonですが、それぞれの投稿データは次のようなスキーマになっています。

  {
    "_id": "5ecce2e5fc19b9004a86ec47",
    "format": "markdown",
    "createdAt": "2020-05-26T09:35:33.830Z",
    "path": "/user/anatofuz/note/2020/05/26",
    "body": "ここに内容が入る",
    "author": "5df5ef37d744a60045dd1524",
    "hasDiffToPrev": true,
    "__v": 0
  }

注目すべきはcreatedAtはタイムスタンプになっており、authorはgrowiのuserのidとなっています。

authorが具体的に何かはrevisionsだけでは決まりませんので、とりあえず今回は放置します。

pathはgrowiのエントリのタイトルです。これはunixのファイルパスと対応してそうなので、生成したmarkdownはこのパスを利用します。

bodyはエントリの内容ですので、bodyをpathに書かれた場所に書き込む方針を取ります。

markdown自体はgit/hgなどのバージョン管理ツールで管理するのを想定するので、createdAtは最新のものを1つだけ使うようにしてみます。

Perlで軽く書く

というわけでPerlで書いてみました。

みんな大好きPath::Tinyを使っています。

metacpan.org

#!/usr/bin/env perl
use strict;
use warnings;

use utf8;
use Encode;
use JSON;
use Path::Tiny;

my $json_file = shift or die 'require json file';
my $revision = decode_json(path($json_file)->slurp);


my $paths;

for my $elem (@$revision) {
   push(@{$paths->{$elem->{path}}}, $elem);
}

for my $path (keys %$paths) {
    my $elems = $paths->{$path};
    print encode_utf8 "$path\n";

    my @sorted_elems   = sort { $b->{createdAt} cmp $a->{createdAt}}  @$elems;
    my $latest_elem    = shift @sorted_elems;

    my $emit_file_path = path("./emit/$path.md");
    $emit_file_path->touchpath;
    $emit_file_path->spew_utf8($latest_elem->{body});
}

__END__
  {
    "_id": "5ecce2e5fc19b9004a86ec47",
    "format": "markdown",
    "createdAt": "2020-05-26T09:35:33.830Z",
    "path": "/user/anatofuz/note/2020/05/26",
    "body": "",
    "author": "5df5ef37d744a60045dd1524",
    "hasDiffToPrev": true,
    "__v": 0
  }

$perl parse.pl revisons.json

の様に使います。

今回は/user/anatofuz/note/2020/05/26の場合は./emit/user/anatofuz/note/2020/05/26.mdというファイルが生成されるようにしてみました。

実際に研究室のGrowiのbackupをもとに実行すると

$ ls emit
611/         Agda.md      Christie/    FileSystem/  Haskell/     Linux/       Sandbox/     software/    user/
611.md       CbC/         Christie.md  Gears/       Haskell.md   Raku/        Sandbox.md   software.md  user.md
Agda/        CbC.md       Events/      Gears.md     Linda.md     Raku.md      growi.md     trash/

の様になっており、例えばRaku.mdを見ると

# Raku

ちょっと前までPerl6と呼ばれていたプログラミング言語

# Docs

- 公式Document
    - https://docs.raku.org/
        - `docs.perl6.org`のものは古いので見ない

# contents

$lsx(/Raku)% 

と無事取れていますね!!!

TODO

まぁ色々雑なのでTODOが多々あります

  • 作者の情報を取得する
  • Hugo等の静的サイトジェネレーターとの組み合わせを考えて、メタ情報をつける
  • 生成パスがこれで良いのか問題
  • できればワンバイナリかfatpackしたい

ということでversion1くらいの話です。まぁこういうの書くの楽しいですよね