ISUCON10に出場して再起動試験で泣いてきました

ISUCON10にチームINJとして学生枠で出場し、結果としては学生本戦出場枠には入っていましたが、再起動試験で失格でした。

isucon.net

今年は最後の学生枠みたいなところがあったので、まぁ思い出づくりも兼ねて id:unimarimo (jogo)と研究室の後輩でありシス管のmkくんと出場しました。ちなみにチームは id:unimarimo以外は初参加。

シス管メンバー集結みたいな形になったのだけど、なんとisuconの予選と大学のネットワーク機器の入れ替え工事日が被ったので関係各所にはご迷惑をおかけしました...。ちなみに今回は各自自宅からzoom繋ぎっぱなしで参戦しました。

ちなみにチーム名INJはIikanji Na Jogo の略です。

f:id:anatofuz:20200913045123j:plain

役割分担としては

  • AnaTofuZ
    • 全体, 声掛け
    • webapp
  • Jogo
    • インフラ全般
  • mk
    • 遊軍

みたいな感じでした。 特にJogoくんがインフラ専門として活躍していて、マルチに活躍できるmkくんが遊軍としていろんな領域を見れていたのは良かった気がする。

やったこと

まとめると

  • チームの声掛け
  • デプロイスクリプトの用意
  • ヤバそうなエラーの解決要因
  • dlvを投入してのデバッグ
  • jsonライブラリの変更
  • 細々したSQLの変更
  • いらんORDER BYの削除とか

このあたりをしました

だいたい時系列ごと

2hの猶予が与えられたので、シェルでデプロイスクリプトをしこむ。わりと便利だったのでやってよかった。

ポータルが508騒ぎの中、「当日マニュアルはdiscordにURLがあるぞ」という話になり、みんなでポータルが復旧するまでマニュアルを音読していた。

「とりあえず椅子と資料請求させとけばええんか」「なるほどな」みたいな話をしていた。あとここでBot対策が重要そうだけどまだ実装されてないとかあるので、実装せないとな.....みたいになった。

ポータル回復後みんなでsshまつりをする。ssh先のIPの計算ができなかったが、mkくんがバシッと運営のconfigをupdateしてくれてそれを使うことになった。ポートフォワーディングのconfigの書き方とsshコマンドの使い方を理解する。

gitリポジトリ化とpprofとgo-sql-proxyをしこみ(mysql:proxyの書きミスとかがあり手間取ったが)大体開始1hちょいくらいには全体のボトルネック等をチームで共有した。アクセスログはalpが上手く読めず、全体を通して活用できてなかった。どうもUserAgent周りとアクセス先のエンドポイントが重要そうだったので、ちゃんと見るべきだった気がする。

pprofの結果json関連とnazotteがネックになっていることがわかったので、golangで使っているjsonライブラリを github.com/goccy/go-json にひとまず切り替えるなどを仕込んだ。そこまでスコアが上がらなかった気もするが...。

github.com

なんとなくテンプレートがねぇなという話になり、そもそもこの画面はどこから来ているかを探したところ、「あっ静的HTMLでjs経由か!?」みたいな話になる。「えっ画像もほとんどこのディレクトリじゃん.......」と連鎖的に確認し、jogoくんにnginxのキャッシュとgzip等の最適化を依頼する。

そういえばbotの解決もするかと思い、軽くググるとuser-agentのブロックはnginx側で出来るらしいので、jogo先輩に脳筋if文コピペを以来する。

nazzoteのN+1を解決すれば伸びそうというのはわかったものの、JOINするわけでもないので「ぐぐぐ......」となり、とりあえずSELECT *している不毛な箇所を消してまわろうという作戦に出た。一箇所の変更は問題なくて、ある程度いい感じになった。

ここで調子に乗って「表示に影響しないJSONの要素も落とすか」となったが、この施策をし始める前後でベンチが何も言わず死ぬ減少が多発する。いろいろロールバックしていたが状況がわからず、運営に問い合わせたり色々していたところ、mkくんがレギュレーションに「JSONの変更はNG」との文脈があることを発見。泣きながらロールバックする。ここでだいぶ時間をとられた......。

jogo先輩とmkくんが作ったindexのsyntax errorを解消していたり、自分のSQLのsyntax errorを解決していたりもしていた。デバッグしたかったので本番にgolang 1.15とdlvをいれて気合で見ていたけれど、このへんがあんま良くなかったかも知れない。

index周りだとSELECT * FROM estate ORDER BY rent ASC, id ASCみたいなクエリのindexを有効化しようと頑張っていたが、FORCE INDEXしないとindex貼られないという事件に遭遇していた。感想を見る限りMySQL8を使うかデータ構造に手をいれるのが大正解だったらしい。ぐぐぐ。。。

終盤でMySQLのクエリキャッシュが結構聞くことがわかったので、2台目をDBサーバーにするという背策をjogoとmkくんに頼み、ベンチマークを回していくと過去最高の1200台を記録。かなり盛り上がった。

レギュレーションで学生が上位25チームに入ればそれを除いた学生上位5チームが本戦出場なので、ギリギリいけるかな〜みたいな話をしていた。結果はギリギリ行ける枠にはいたのだけど、再起動試験で失格でした。再起動チャレンジやってなかったので痛いところ。まぁ再起動しても気づいたかどうかは別っぽい。

所感

ということで学生最初で最後のisuconでした。チーム運用もめちゃくちゃ良くて、ギスギスせずに進められたので良かった気がする。(configのコピペ忘れをしたjogo先輩に「舐めプか?」と煽ったのはノーカン)

全然俺はSQLもHTTPもわからん.....なにも...........。みたいになったので、webの勉強ちゃんとしないとなぁと思いました。細々した修正を中心にやってしまい、もう少し責めのコミットができるくらいの知識が必要でした。特に序盤ではsqlx関係のエラーを多発させてしまい、「sqlライブラリの慣れと、SQLの知識が必要だったな.....」と痛感しています。N+1は最初に気づいたにも関わらず、解決できなかったのが痛いです。他にもなんとなく改善できそうなとこは何個かあったけど、歯が立たなかったです。。。nazotteの改善でいろいろミスを踏みましたが、dlvいれてからは冷静に対処できたのでそこは良かった、そもそもミスをしたのは、経験値不足なのでツラミですね。。。

他にはテーブルが2つなのでDBを分けるという話がrandomチャンネルにありましたが、思いつかなかったですね。

あんまり練習時間が取れなかったチーム(全員シス管なので)+初めてのisuconでしたが健闘できたのは良かったですね。もちろん学生上位とのスコア差は大きいですが、普段からweb専門にやってる人がいないチームで1000点超えれたのは大きい気がする。これはid:unimarimoとmkくんの働きがすごかったというのがほとんどですね。これで本戦に行っていればもっと良かったので再起動試験が悔やまれます。一度再起動試験を試すべきだったな.....。ちなみに再起動試験のどこで死んだのかは調査中です。(どうもベンチで使ってたサーバー以外の方をみられていたらしく、そっちはnginxを動かしていた為に勘違いされた or そいつがDBを使いにいってベンチ側のサーバーからDBにアクセスできなかったのではという話を推測しています)

全員就職先の土地がバラバラなので、次回このチームになることはオンライン開催でない限り無理ですが、チームとしても良い経験になったかなと思います。シス管の作業頑張っていこうな...。

チーム関連だと他メンバーへの声掛けとか詰まってるとこを協力して解決ができてよかったです。ISUCON夏期講習の「手が止まっている時間を作らない」がちゃんとできたのはチームに貢献できた点かなぁと思います。

次回のisuconがあれば社会人枠ですが、ここで本戦出場目指してやっていきましょう。バンバンスコア上げたいしもっと貢献したいね。。。。現場からは以上です。

openldapはldapsの代わりにldapを使っていても死なない

今日のツラミポイント。

学科で運用しているOpenLDAPサーバー(slapd)の設定(slapd.conf)で詰まったやつ。 別で作成したslapdをプロバイダにして、いままでプロバイダだったslapdをコンシューマーにしようと、設定を書いていた。

overlay syncprov

syncrepl rid=104
        provider=ldap://example.com:389
        bindmethod=simple
        binddn=""
        credentials=""
        searchbase="ou=ie,o=u-ryukyu,c=jp"
        schemachecking=on
        type=refreshAndPersist
        retry="60 10 300 3"
        tls_reqcert=never
        interval=00:00:00:01


# ミラーモードを有効化
mirrormode on

これでsystemctl restart slapdすると、何もエラーを出さず元気に立ち上がるものの ldapsearch -x uid=anatofuzとかすると綺麗に何も出ていない。 どうもデータベースになにも書き込まれていない状態だった。

ldapsearch-Hでプロバイダを指定するとちゃんと引けるので、困ったね.....となっていたが、落ちはタイトルの通りで

        provider=ldap://example.com:389

ではなく

        provider=ldaps://example.com:389

じゃないといけないというショッパイ落ち.........。気をつけようね。。。。。

ubuntu上にOpenLDAPの構築

学科というか琉大では学生のアカウント管理にLDAPを使っている。 学科の場合はセンター(大本)のLDAP情報を同期した、学科システム上に構築したslapdをLDAPサーバーとして活用している。

今年は学科システムの式年遷宮の年なので、Centosで従来動いていたシステムを参考に、UbuntuOpenLDAPを構築した。

すでにDocker環境はakatsukiの構築で作っていたのだけれど、とりあえず安心感のあるVMopenldapの構築をしたかったので、ansibleを書いていた。

init.ldifとかは現在動いているLDAPのバックアップ。

---
- name: install ldap
  become: yes
  apt:
    name:
      - slapd
      - ldap-utils
  environment:
    DEBIAN_FRONTEND: noninteractive

- name: copy ldap.conf
  become: yes
  copy:
    src: ldap.conf
    dest: /etc/ldap/ldap.conf
    owner: openldap
    group: openldap
    mode: 0664

- name: copy slapd.conf
  become: yes
  copy:
    src: slapd.conf
    dest: /etc/ldap/slapd.conf
    owner: openldap
    group: openldap
    mode: 0640

- name: copy schemas
  become: yes
  copy:
    src: schema
    dest: /etc/ldap/
    owner: openldap
    group: openldap
    mode: 0644

- name: copy init.ldif
  become: yes
  copy:
    src: init.ldif
    dest: /tmp/init.ldif
    owner: openldap
    group: openldap
    mode: 0664

- name: copy DB_CONFIG
  become: yes
  copy:
    src: DB_CONFIG
    dest: /var/lib/ldap/DB_CONFIG
    owner: openldap
    group: openldap
    mode: 0664

- name: remove /etc/ladp/slapd.d/cn=config
  become: yes
  file:
    path: /etc/ldap/slapd.d/cn=config
    state: absent

- name: slapadd -f /etc/ldap/slapd.conf
  become: yes
  command: echo '' | slapadd -f /etc/ldap/slapd.conf

- name: stop slabd
  become: yes
  systemd:
    name: slapd
    state: stopped
    daemon_reload: yes
    enabled: yes

- name: chown cn=config
  become: yes
  file:
    path: /etc/ldap/slapd.d/cn=config
    state: directory
    recurse: yes
    owner: openldap
    group: openldap

- name: chown /var/lib/ldap
  become: yes
  file:
    path: /var/lib/ldap/
    state: directory
    recurse: yes
    owner: openldap
    group: openldap

- name: slaptest
  become: yes
  become_user: openldap
    # ignore_errors: yes
  command: slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d

- name: slapadd -l /tmp/init.ldif
  become: yes
  command: slapadd -l /tmp/init.ldif


- name: enable slabd
  become: yes
  systemd:
    name: slapd
    state: restarted
    daemon_reload: yes
    enabled: yes

openldapはaptでslapdを指定すれば入るけれど、 DEBIAN_FRONTEND: noninteractiveを指定しないと上手く入らない(唐突にGUI的なあの画面が出てくる)のが注意点。

詰まったのは slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.dする時にopenldapユーザーでやらないと、/var/lib/ldapの中で生成されるBerkeley DBのパーミッションが変になること。

解決方法としてはbecome_useropenldapを指定し、プロジェクトルートのansible.cfgに次の用に書く。

[defaults]
allow_world_readable_tmpfiles=true

微妙に再起動するとパーミッションが異なるとかはあったが、まぁ大丈夫だろうと信じている。。。

netplanは拡張子にうるさい

netplanがapplyしても全然使い物にならなくてキレていたが拡張子が問題だった。

netplanの設定ファイルはYAMLで書くのだけれど

/etc/netplan/ccc.yml

だとだめで

/etc/netplan/ccc.yaml

じゃないとだめらしい。こういうので人生消費したくない........

新鮮なFedora32にwordpressを構築するansbile

ということで書きました。OSの課題で使ういつものやつです。

ie.u-ryukyu.ac.jp

もともとは先輩が書いていたものですが、今年はシステム更新などでapacheを学科から追放したい気分があったのでnginxでリライトしました。といいつつ大体先輩のコードをそのまま使ってます。(ありがとうございます)

otnk1122.hatenablog.com

Fedora32で動くのを確認したので新鮮なFedoraでどうぞお試しください。

色々と改善していて、まずtasksに書いていたものをrolesにしたりとか、デーモンの再起動にnotifyを使っています。このあたりはisuconのansibleを参考にしました。

nginxでwordpressを動かしたいのでphp-fpmを入れていますが、本当にunix domain socketで通信をしているかが怪しい気がする。多分大丈夫だとは信じたいですが。

なにかツッコミがあればよろしくお願いします。(それを改善するのがB2の課題になる気がする)

apacheからnginxへの移行時の証明書周りの設定

歴史的な理由もあり学科のweb周りはapacheが元気に稼働しているケースが多い。

とはいえもうapacheの面倒を見るよりはnginxに移行したほうが何かと便利そうなので、現在apacheからnginxへの乗り換えを仕込んでいる。

先週の台風の際に学科システムへのダメージを減らすために、学科システムを一時的に停止させ、クラウドにおいてあるwebの予備サーバーを使うことになった。その時にタイミングを合わせて試しにnginxでwebを構築しようとしたところ、ちょっとSSL周りで詰まったのでそのメモ。

apache側では次のように証明書を指定している。

SSLCertificateChainFileはいわゆる中間証明書。

SSLCertificateFile /etc/pki/tls/private/ie.u-ryukyu.ac.jp/ie.u-ryukyu.ac.jp.cer
SSLCertificateKeyFile /etc/pki/tls/private/ie.u-ryukyu.ac.jp/ie.u-ryukyu.ac.jp.key
SSLCertificateChainFile /etc/pki/tls/private/ie.u-ryukyu.ac.jp/nii-odca3sha2ct.cer

ググったところ SSLCertificateChainFileに対応するnginxの設定項目は無いらしい。

どうもnginxの場合はSSL証明書と中間証明書を連結させたファイルを作る必要がある模様。

blog.steven266.de

ということでこんな感じのコマンドを実行して連結させたpemファイルを生成する。

$cat ie.u-ryukyu.ac.jp.cer nii-odca3sha2ct.cer > ssl.pem

生成したpemファイルをnginxのserverコンテキスト内に指定する。

    ssl_certificate /etc/pki/tls/private/ie.u-ryukyu.ac.jp/ssl.pem;
    ssl_certificate_key /etc/pki/tls/private/ie.u-ryukyu.ac.jp/ie.u-ryukyu.ac.jp.key;

こうするといい感じに動く。証明書周り難しいね...。

ansibleでpackageのinstall時にloopは使わないほうが良い

教えてもらったので。

ansibleでyum,dnf, apt を使っていくつかのpackageをまとめてインストールする際に、癖でloop(with_items)を使っていたのだけど、これは非推奨らしい。

というのも

 - name: install dnf packages
   become: yes
   dnf:
    name:
      - "{{ item }}"

     state: latest
  loop:
    - mariadb
    - mariadb-server
    - MySQL-python3
    - python3-libselinux

みたいに書いてしまうと、これはこのコマンドを実行していることと等価になる。

sudo dnf install mariadb
sudo dnf install mariadb-server
sudo dnf install MySQL-python3
sudo dnf install python3-libselinux

そんな事しなくても sudo dnf install mariadb mariadb-server MySQL-python3 python3-libselinuxってまとめられるよね。こっちのほうが効率いいよねということで、loopを使うやり方は非推奨とのこと。

じゃあ実際にはどう書けばいいのかというと、usageを見たところ次のような世界観らしい。

- name: ensure a list of packages installed
  yum:
    name: "{{ packages }}"
  vars:
    packages:
    - httpd
    - httpd-tools

- name: Install a list of packages
  yum:
    name:
      - nginx
      - postgresql
      - postgresql-server
    state: present

- name: install the latest version of Apache and MariaDB
  dnf:
    name:
      - httpd
      - mariadb-server
    state: latest

- name: Install a list of packages
  apt:
    pkg:
    - foo
    - foo-tools

ということでnamesの後ろに複数リストで与えるか、 {{ packages }}を使えとのこと。

debug情報を見たところnamesで複数指定する方法とpackagesを使うやり方は内部的には同じみたいですね。-vvvで見たところ両方次の様なjsonが表示されていました。好きな方を選べという感じっぽい。

ok: [test-fedora] => {
    "changed": false,
    "invocation": {
        "module_args": {
            "allow_downgrade": false,
            "autoremove": false,
            "bugfix": false,
            "conf_file": null,
            "disable_excludes": null,
            "disable_gpg_check": false,
            "disable_plugin": [],
            "disablerepo": [],
            "download_dir": null,
            "download_only": false,
            "enable_plugin": [],
            "enablerepo": [],
            "exclude": [],
            "install_repoquery": true,
            "install_weak_deps": true,
            "installroot": "/",
            "list": null,
            "lock_timeout": 30,
            "name": [
                "mariadb",
                "mariadb-server",
                "MySQL-python3",
                "python3-libselinux"
            ],
            "releasever": null,
            "security": false,
            "skip_broken": false,
            "state": "latest",
            "update_cache": false,
            "update_only": false,
            "validate_certs": true
        }
    },
    "msg": "Nothing to do",
    "rc": 0,
    "results": []
}