hoge な blog

メモとかtipsとか

Cloudflare Tunnel を使って自宅サーバを公開する

Cloudflare Tunnel を使って自宅サーバを公開する

こんにちは!
42 Tokyo Advent Calendar 2022 の15日目を担当する, akito です。
よろしくおねがいします。

この記事では、以前から個人的に気になっていた Cloudflare Tunnel というサービスについて紹介する予定です。
会社などの組織ではなく、個人的な用途に使う際に気になりそうなことについて纏めています。
この製品が売りにしていそうなセキュリティ的な側面についてはあまり触れない予定です。
主に使いやすさ、使い方などについて書く予定です。

感想以外の内容はドキュメントに書いてあります。
エッセンスを薄く薄くしてブログにした感じです。

(typoや不適切な表現、気になるところなどあればコメントいただけると嬉しいです)

雑なまとめ

  • webサーバを公開できる。設定も比較的シンプル。クライアントの設定も不要。良い。
  • SSHサーバも公開できる、すごい。ただクライアントの設定が必要。
  • 任意のTCPで動くようなサーバについても公開できる。これもクライアントの設定が必要。
  • 実はドメイン名を所有していなくても、一時的なURLを発行してHTTPサーバを公開する機能がある。とてもすごい。

想定する読者

  • private IP アドレスのみ振られているサーバをインターネットに公開したい人
  • ngrok の代替サービスを探している人

何について扱うのか

  • Internet から reacable でないサーバで実行しているサービスにアクセスさせる方法
  • Cloudflare Tunnel に対する個人的な所感

何について扱わないのか

  • セキュリティに関する事項
  • サービスに関連する知識

使用するソフトウェアのバージョン

  • Ubuntu 22.04 LTS (macOSを使用している方は適宜コマンドを読み替えてください)
  • cloudflared version 2022.11.1

前提知識

Cloudflare Tunnel とは

developers.cloudflare.com

Cloudflare Tunnel は、Cloudflare 社が提供しているサービスです。
(Cloudflare社は、CDNやインターネットセキュリティサービス、DDoS防御、DNSに関するサービスなどを提供するアメリカの企業です。)

公式サイトに記載されている Cloudflare Tunnel についての説明をDeepLで訳します。

Cloudflare Tunnel provides you with a secure way to connect your resources to Cloudflare without a publicly routable IP address. With Tunnel, you do not send traffic to an external IP — instead, a lightweight daemon in your infrastructure (cloudflared) creates outbound-only connections to Cloudflare’s edge. Cloudflare Tunnel can connect HTTP web servers, SSH servers, remote desktops, and other protocols safely to Cloudflare. This way, your origins can serve traffic through Cloudflare without being vulnerable to attacks that bypass Cloudflare.

"Cloudflare Tunnelは、パブリックにルーティング可能なIPアドレスを持たずに、お客様のリソースをCloudflareに安全に接続する方法を提供します。Tunnelでは、外部IPにトラフィックを送信するのではなく、お客様のインフラ(cloudflared)内の軽量デーモンがCloudflareのエッジへのアウトバウンド専用の接続を作成します。Cloudflare Tunnelは、HTTPウェブサーバー、SSHサーバー、リモートデスクトップ、その他のプロトコルをCloudflareに安全に接続することができます。これにより、お客様のオリジンはCloudflareを迂回する攻撃に対して脆弱になることなく、Cloudflareを通じてトラフィックを提供することができます。"

今回着目しているのは"パブリックにルーティング可能なIPアドレスを持たずに、お客様のリソースをCloudflareに安全に接続する方法を提供します。"という特徴です。

Cloudflare Tunnel は以下のプロトコルをサポートしています。(参考:Ingress rules (#Supported Protocol) | Cloudflare Docs)

今回はこれらのうちいくつかのプロトコルについて、どのようにセットアップするのかを体験し、その中で得た感想についてまとめようと考えています。

制約

  • 自分が何らかのドメイン名を所有しており、そのドメイン名を管理する権威DNSサーバとしてCloudflareを利用している

(※)必ずしも Cloudflare レジストラを通じてドメイン名を購入する必要はないのですが、
Cloudflare レジストラを通じて購入した場合、DNSのセットアップが簡単になるようです。

(※)実はHTTPに限って言えばドメイン名を所有している必要はなかったりします。

(筆者がDNSに詳しくなく、誤った説明をしそうなので、)DNSに関する説明は省きます。
以下のドキュメントが参考になると思います。

動作の概要

Cloudflare Tunnel (#how-it-works) | Cloudflare Docs

Cloudflare は有名なCDNサーバ運用事業者のひとつです。
そのため、世界中に Cloudflare が提供している Cloudflare Edge Server が存在しています。
Cloudflare Tunnel は、Cloudflare Edge Server と自分の管理しているサーバとの間にトンネルを確立することで、
public にルーティング可能なIPアドレスを持っていないサーバに対して外部から接続する方法を提供します。

ドキュメントの図が非常を見ると理解しやすいと思います。
(ちなみにこのドキュメントの図はwebサーバを公開する例になっています。)

図の引用元: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/#how-it-works

以下、ドキュメントが見つけられなかったので厳密性には欠けるのですが、大体こんな感じで動作しているんじゃないかという予想を書いてみます。
分かりやすさのため断定表現を使っていますが、間違っている可能性があることに注意してください

トンネルを作成した時点で <tunnel-id>.cfargotunnel.com というcfargotunnel.com のサブドメインがCloudflareのネットワーク上に作成されます
サーバの管理者は <tunnel-id>.cfargotunnel.com をターゲットとする CNAME record を作成します
クライアントは CNAME record に登録されているホストに対して HTTPSリクエストを送信します。
すると、Cloudflare Network 上でSSLが終端され、自分のサーバの cloudflared にHTTPリクエストが転送されます。(もちろんトンネルを経由して転送されます)
cloudflared は自分のサーバのどのportでwebサーバが動いているのかを知っているため、webサーバにリクエストを転送します
あとはwebサーバがリクエストを処理してレスポンスを返し、逆順にたどるとクライアントがレスポンスを受け取れます。
以上の流れで internet から reachable でないwebサーバを公開することができます。

HTTP をトンネリングする

では手始めに HTTP をトンネリングしてみましょう。
以下のドキュメントを参考に web サーバを公開してみます。

手順

0. 前提要件

1. サーバに cloudflared をインストールする

cd /tmp
curl -LO https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64
chmod +x cloudflared-linux-amd64
sudo mv cloudflared-linux-amd64 /usr/local/bin/cloudflared

2. Cloudflare account にログインする

cloudflared login

上記のコマンドを使って、サーバの認証を行います。
まず、ブラウザが起動するので、自分のアカウントにログインします。
その後、(0.前提条件 の項で)自分が追加したドメイン名を選択します。
すると、証明書がダウンロードされます(デフォルトだと ~/.cloudflared/cert.pem にダウンロードされる)

3. Cloudflare Tunnel を作成する
cloudflared tunnel create <tunnel-name>

# 例
cloudflared tunnel create test-tunnel-42

上記のコマンドを使ってトンネルを作成します。
コマンドによってトンネルを作成すると、トンネルのための credentials file が一緒に生成されます。

4. cloudflared を設定する

url: http://localhost:8080
tunnel: <Tunnel-UUID>
credentials-file: /path/to/.cloudflared/<Tunnel-UUID>.json

~/.cloudflared/config.yaml に設定ファイルを置きます。
上記の例では、webサーバを localhost:8080 で公開することを想定しています。
もし異なるportを使用する場合は適宜変更してください。

また、複数のwebサーバを公開したい場合は以下のドキュメントを参考にしてください。

5. CNAME record を追加する

cloudflared tunnel route dns <tunnel-name or tunnel-UUID> <hostname>

# 例1
cloudflared tunnel route dns 123e4567-e89b-12d3-a456-426614174000 test-42.example.com
# 例2
cloudflared tunnel route dns test-tunnel-42 test-42

上記のコマンドによって CNAME record を登録します。

参考: DNS Record | Cloudflare Docs

6. Cloudflare Tunnel を実行する

cloudflared tunnel run
cloudflared tunnel --config ~/.cloudflared/config.yaml run

上記のコマンドにより、Cloudflare Edge と cloudflared との間にトンネルを確立します。
ちなみに system service として(daemonとして)動作させることができたりもします。
以下のドキュメントを参考にしてください。

動作確認

私の環境で動作確認をしてみます。
akkyorz.dev というドメイン名をCloudflareの権威DNSサーバで管理しています。

サーバ側の準備

# http://localhost:8080 で webserver を立てておく
docker run -it --rm -d -p 8080:80 --name web nginx

# cloudflare tunnel を確立する
cloudflared tunnel login
cloudflared tunnel craete test-tunnel-42
vim ~/.cloudflared/config.yaml
# edit
cloudflared tunnel route dns test-tunnel-42 test-42
cloudflared tunnel run

クライアント側の準備

ブラウザからHTTPSリクエストを送信した結果は以下のようになります。
クライアントの設定は不要です。(多分)

クライアントのブラウザからHTTPリクエストを送信した結果

ちなみに証明書には Let's Encrypt のワイルドカード証明書が使われていました(*.akkyorz.dev)

感想

ということで、HTTPサーバをglobalに公開してみました。
比較的簡単にHTTPサーバを公開することができたかなと思います。

では、次に行ってみましょう。

SSHをトンネリングする

外出先から自宅のPCで作業したりしたくなったりするときがありますよね。
でも、インターネットから到達可能なIPアドレスを持っておらず困っていました。
Cloudflare Tunnel を使えばこの問題を解決できます。
ということでSSHをトンネリングしてみましょう。
以下のドキュメントを参考にSSHサーバにアクセス可能な状態にしてみます。

手順

サーバ側

大体の手順は HTTP をトンネリングする手順と共通です。
異なるのは 設定ファイル の書き方です。
サーバ側の説明は省略します。
Ingress rules | Cloudflare Docs を参考にしてください。
動作確認で具体例を出して説明しています。

クライアント側

HTTP/HTTPSサーバの場合と異なり、SSHサーバにアクセスするためには、クライアント側でセットアップが必要です。
2通りの方法があります。

  1. WARP(クライアントアプリ)を使う
  2. Cloudflare Access(cloudflared コマンド)を使う

今回は 2. Cloudflare Access を使う 場合を説明します。

Cloudflare Access という機能を使用します。

1. クライアントに cloudflared を install する
cd /tmp
curl -LO https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64
chmod +x cloudflared-linux-amd64
sudo mv cloudflared-linux-amd64 /usr/local/bin/cloudflared
2. クライアントの ~/.ssh/config を設定する
Host ssh.example.com
    User <login user>
    IdentityFile </path/to/private_key>
    ProxyCommand /path/to/cloudflared access ssh --hostname %h
3. sshコマンドを実行する
ssh <username>@ssh.example.com

動作確認

折角なのでHTTPサーバを動かしたまま書き換えてみましょう。
以下の設定も併せて確認しながら進めます。

サーバ側の準備

サーバ側の設定をする

0. 前提条件
  • "HTTP をトンネリングする" の設定が完了している
  • SSHサーバが port 22 で動作している

...

4. cloudflared を設定する
tunnel: <Tunnel-UUID>
credentials-file: /path/to/.cloudflared/<Tunnel-UUID>.json
ingress:
    - hostname: test-42.akkyorz.dev
      service: http://localhost:8080
    - hostname: test-ssh-42.akkyorz.dev
      service: ssh://localhost:22
    - service: http_status:404

複数のサービスを動かします。
どうやら、ホスト名が一致した場合のトラフィックをオリジンにルーティングしてくれるようです。

5. CNAME record を登録する
cloudflared tunnel route dns test-tunnel-42 test-42-ssh
6. Cloudflare Tunnel を実行する
cloudflared tunnel run

クライアント側の準備

# install cloudflared
cd /tmp
curl -LO https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64
chmod +x cloudflared-linux-amd64
sudo mv cloudflared-linux-amd64 /usr/local/bin/cloudflared
cloudflared -v

# ssh config の設定
vim ~/.ssh/config

動作確認

sshを実行してみます。

$ ssh test-42-ssh.akkyorz.dev
Welcome to Ubuntu 22.04.1 LTS (GNU/Linux 5.15.0-56-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

0 updates can be applied immediately.

Last login: Wed Dec 14 23:31:56 2022 from 127.0.0.1

ログを見ると、 127.0.0.1 からリクエストが来ていることがわかりますね。

ちなみにhttpリクエストも問題なくレスポンスが返ってきます。

感想

という感じで、こちらも比較的簡単にセットアップすることができました。

ProxyCommand によって cloudflared access コマンドが裏で実行されるため、設定さえしてしまえば普段トンネルを意識せずに作業することが可能です。
特に Cloudflare Access に関係する認証の設定も行う必要がなく、簡単にセットアップできます。

一方で、(比較的軽い作業ですが)cloudflared コマンドをinstallする必要があるため、気になる人は気になるかもしれません。
また、誰でもSSHできる状態になってしまうため、セキュリティ的な観点で問題がありそうです(セキュリティの問題は、Cloudflare Zero Trust の機能群を使うと解決できるかもしれませんが、詳細は調査していません。)

任意のTCP

ということで、HTTP/SSHについてはアクセスできることがわかりました。
では、任意のTCPで動作するサービスにアクセスさせる方法について説明します。

といっても、HTTP/SSHの時と大きな手順の変更はありません。
以下のドキュメントを参考にします。

Arbitary TCP | Cloudflare Docs

手順

サーバ側

SSHの場合と同様に設定ファイルを書き換えればOKです。
Ingress rules | Cloudflare Docs を参考にしてください。
動作確認で具体例を出して説明しています。

クライアント側

1. クライアントに cloudflared を install する

前で説明したため省略します。

2. クライアントからcloudflare access を実行する
cloudflared access tcp --hostname tcp.site.com --url localhost:9210

上記のコマンドで接続します。 これで準備ができました。 client は、 localhost:9210 にアクセスすると、 1. localhost:9210 2. client の cloudflared 3. Cloudflare Edge 4. serverの cloudflared 5. cloudflared に設定されているingress

のようにリクエストが転送されるのだろうと想像しています。
(手前味噌で非常に恐縮ですが、下記のブログにSSHのDynamic Forwardingを使ったトンネルの例を図にまとめているので、もしかしたら参考になるかもしれません)

akkyorz.hatenablog.com

動作確認

それでは動作確認です。

mysqlをhostingしようと思います。

サーバ側の準備

前提条件
  • “HTTP をトンネリングする” の設定が完了している
  • "SSHサーバをトンネリングする" の設定が完了している
0. mysqlを立ち上げる
$ docker run --name test-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -p 3306:3306 -d mysql:8.0
$ mysql -u root -h 127.0.0.1 -p
mysql>

...

4. cloudflared を設定する
tunnel: <Tunnel-UUID>
credentials-file: /path/to/.cloudflared/<Tunnel-UUID>.json
ingress:
    - hostname: test-42.akkyorz.dev
      service: http://localhost:8080
    - hostname: test-42-ssh.akkyorz.dev
      service: ssh://localhost:22
    - hostname: test-42-mysql.akkyorz.dev
      service: tcp://localhost:3306
    - service: http_status:404
5. CNAME record を登録する
cloudflared tunnel route dns test-tunnel-42 test-42-mysql
6. Cloudflare Tunnel を実行する
cloudflared tunnel run

クライアント側の準備

# install cloudflared
# 省略

# cloudflared access
cloudflared access tcp --hostname test-42-mysql.akkyorz.dev --url localhost:3306

準備完了

動作確認

$ $ mysql -uroot -h 127.0.0.1 -p
Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 10
Server version: 8.0.31 MySQL Community Server - GPL

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [(none)]>

動きました。

感想

TCPでも動くんですねぇ...すごい。
ただ、クライアント側で毎回コネクションを張る動作をする必要があるので、そこがちょっと手間かもしれません。
(ドキュメントでは、Desktopショートカットに登録しておけばコマンドラインに毎回打たなくても大丈夫、と紹介されていました)

Quick Tunnels

Quick Tunnels | Cloudflare Docs

では最後に、 気になった機能について紹介します。
実はdomainを持っていなくても(Cloudflareの権威DNSサーバで自分の所有するドメイン名を管理していなくても)、簡単にlocalの環境を公開する機能があります。

手順

実行方法も簡単です。
以下のコマンドで実行できます。

# http://localhost:8080 に流す
cloudflared tunnel

# 違うportを指定したい場合
cloudflared tunnel --url http://localhost:7000

ちなみに、~/.cloudflared/config.yaml が存在しているとこのコマンドは実行できないため注意が必要

(検証していないのですが、おそらくコマンドのオプションである --url というキーワードを見る限り、HTTPしか対応していないのではないかと想像しています)

動作確認

# 8080 で HTTP を待ち受ける
$ docker run -it --rm -d -p 8080:80 --name web nginx

# Quick Tunnel を作成
$ cloudflared tunnel
2022-12-14T14:56:05Z INF Thank you for trying Cloudflare Tunnel. Doing so, without a Cloudflare account, is a quick way to experiment and try it out. However, be aware that these account-less Tunnels have no uptime guarantee. If you intend to use Tunnels in production you should use a pre-created named tunnel by following: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps
2022-12-14T14:56:05Z INF Requesting new quick Tunnel on trycloudflare.com...
2022-12-14T14:56:07Z INF +--------------------------------------------------------------------------------------------+
2022-12-14T14:56:07Z INF |  Your quick Tunnel has been created! Visit it at (it may take some time to be reachable):  |
2022-12-14T14:56:07Z INF |  https://endangered-allocated-tribes-prison.trycloudflare.com                              |
2022-12-14T14:56:07Z INF +--------------------------------------------------------------------------------------------+
2022-12-14T14:56:07Z INF Cannot determine default configuration path. No file [config.yml config.yaml] in [~/.cloudflared ~/.cloudflare-warp ~/cloudflare-warp /etc/cloudflared /usr/local/etc/cloudflared]
2022-12-14T14:56:07Z INF Version 2022.11.1
2022-12-14T14:56:07Z INF GOOS: linux, GOVersion: go1.19.3, GoArch: amd64
2022-12-14T14:56:07Z INF Settings: map[protocol:quic]

結果はこんな感じです。一回証明書エラーがでたのですが原因を調査するのを忘れていました。

Quick Tunnels で生成された URL にアクセスした結果

感想

ドメイン名を用意しなくてもHTTPサーバを公開できるのは便利だなと思いました。
誰でもアクセス可能ではあるため、セキュリティ的な懸念はあるかもしれないのですが、個人開発などの開発環境などをシュッと共有したりするのに役立ちそうです。

cloudflared tunnel run を実行しようとして誤って cloudflared tunnel コマンドを実行し、何か怪しいURLが出力されたな(?)と思って調べていたら出てきた機能でした。

おわりに

いかがだったでしょうか。 Cloudflare Tunnel の機能を使ってみた作業ログと、それについての感想についてまとめました。
便利そうかなと思うので、気になった方はぜひ試してみてください。

明日は @snara-42 さんが「stack1つでチューリング完全なyasl」という記事を書いてくれるようなので、ぜひそちらも読んでみてください!
最後まで読んでいただきありがとうございました。

参考資料

ISUCON12予選の参加記

こんにちは、akitoです。

team: using namespace std; で参加しました。(5・7・5) (members: hidollara, sh4869)
誰と出ようかな〜と思っていたのですが、二人ともお誘いしたら快く参加を決めてくれて感謝です。

本記事では、ISUCON12予選の高速化のために行った手順についてのメモを残します。

リポジトリ: using-std/isucon12q

事前準備

使用したミドルウェア・コマンドの類は以下のとおりです。 資料やOSSなどの著作物を公開してくださった皆様に感謝します。:bow:

  • alp
  • pt-query-digest
    • MySQLのクエリを集計して解析するツール
  • kaz/pprotein
    • さくらのクラウド上に立てて使用しました
    • ログを集計し、ブラウザ上で確認できるようにする全部入りツールです
    • めちゃくちゃ便利でした。
    • (当日の問題ではSQLiteを使う箇所があったため、その部分はサーバのコンソール上で確認しました)
  • Discord
    • はい

当日

役割分担

メインの役回りは以下のとおりです。
自分はほとんど点数に貢献できなかったので、本当に感謝です。

  • akito: インフラ・設定周り(logとかDB分割とか)
  • hidollara: アプリケーション周り(キャッシュとかIndexとか)
  • sh4869: アプリケーション周り(N+1とかbulk insertとか)

サーバ構成

最終的な構成は、以下のとおりです。

  • Host1 nginx + app + SQLite(tenant)
  • Host2 MySQL(admin)
  • Host3 なし(本当はSQLiteを剥がしたかった)

全体の時系列

  • 10:00~
    • マニュアルを読み
    • git push
    • deploy用のMakefileの作成
    • pproteinの設定の有効化
    • (2717点)初回ベンチ
  • 12:00~
    • (3376点)Host2にmysqlを分ける
    • (4004点)sqliteにndexを追加する
    • (????点)SQLiteJSONログをはく
      • JSONログを集計する JSコードも同時に作成
    • (????点)InterpolateParams=true の設定を追加
  • 13:30~
    • (5606点)dispenseID() でuuidを使う
    • (????点)playersの投入をbulk insertする
    • SQLiteで管理されているデータを /initialize 時に更新しようとする
      • SQLite*.db ファイルごとにテーブルを全てreadして、その結果を逐一Bulk insertした
      • 33秒くらいかかっていたので、ちょうどタイムアウトしてしまう
      • revert
    • この辺から"write tcp 127.0.0.1:3000->127.0.0.1:38182: write: broken pipe" みたいなログが出てくる
  • 16:00~(この辺からメモが怪しい)
    • JWTKeyをメモリ上にキャッシュ
    • RankingのN+1の解消
    • middleware類のチューニング
      • max connection とか file limit とか
  • 17:00~
    • 再起動試験
    • app×2構成にするか検討するが、実装が間に合うか&可能なのか がわからず、断念
    • "write tcp 127.0.0.1:3000->127.0.0.1:38182: write: broken pipe" みたいなログが出ており、減点されていたが、原因が解明できず終了
    • logの停止
    • お祈りフェーズ

やりたかったこと

  • JSONログの停止
    • 忘れてた
  • 3台目をDBサーバとして使う
    • MySQLにdumpしたかった
    • (NFSでマウントして使うとかもあったらしい、気づかなかった)
  • error: broken pipe の解消

おわりに

SQLiteに面食らってしまいましたが、とても面白い問題でした。 エラーの解消ができなかったのが心残りです。

再起動試験は通っているんでしょうか...
インフラ面で強くなりたい(願望)。

ICTSC2021 夏の陣に参加しました

はじめに

icttoracon.net

2021年08月の28(土), 29(日)で開催された ICTトラブルシューティングコンテスト2021 夏の陣(ICTSC2021 Summer) に参加しました。

チーム名は「add忘れ VLAN全消し 疎通なし」です。 本番は自分含め3人で参加しました。

(2021/08/30追記) 全体4位でした。入賞圏内だったと思うので、悔しいですね... ただ、本番終了のタイミングでは6位だったのですが、最後にチームメイトが提出した解答が部分点を貰うことができたようで4位に上がることができたようです。 チームの皆さんありがとうございました🙇

f:id:AkkyOrz:20210830215902p:plain
4位を取ることができました

備忘録的なものとしてどんな問題をどう解いたのかを書き残したいと思います。 (後から問題が公開されると思うんですが、環境自体は自分の手元で再現出来ない可能性が高そうなので、覚えている限り思考の手順が残せれば嬉しいです)

私が解いた問題は以下のとおりです。

  • 2 頑固なindex.html(150)
  • 6 iscsi targetにログインできない!(200)
  • 8 アドレスが配布できない (200)
  • 9 止まらない (50)
  • 11 ャッシュサーバ立てたけど(250)
  • 12 Webサイトにアクセスできない(200)
  • 13 サーバ天気予報 (150)
  • 14 Webサーバが立ち上がらない(50)
  • 15 ンジンエックス(150)

2 頑固なindex.html (150点)

/var/www/html/index.htmlを変更した後、マシンを再起動すると元のファイルに戻ってしまう、という現象が生じていました。 最初はcronとかで上書きしているのかと思ったのですがcronには何も定義されていませんでした。 ならばサービスが何かやっているのかなと思い、/etc以下を探してみると、/etc/tmpfiles.d/ictsc.confに怪しげな記述が見つかりました。

/etc/tmpfiles.d/ictsc.conf

F /var/www/html/index.html 0777 root root - Welcome to ICTSC

どうやらsystemd-tmpfilesが悪さをしていたようです。 上記の部分をコメントアウトすることで、解決することが出来ました。

6 iscsi targetにログインできない! (200点)

iscsi何も分からない...と言いながら解いていました。

iscsi-targetに対してclientからログインしようとするシナリオとなっており、 以下のコマンドを打ってログインを試みたところ、ログインに失敗してしまう、というを解消する問題でした。

client$ sudo iscsiadm -m node --login
Logging in to [iface: default, target: iqn.2021-07.192.168.18.2:target, portal: 192.168.18.2,3260] (multiple)
iscsiadm: Could not login to [iface: default, target: iqn.2021-07.192.168.18.2:target, portal: 192.168.18.2,3260].
iscsiadm: initiator reported error (24 - iSCSI login failed due to authorization failure)
iscsiadm: Could not log into all portals

iSCSI login failed due to authorization failure と怒られていることから、認証系の何かが間違っていそうだと見当をつけました。

iscsi-target$ $ sudo targetcli
/iscsi/iqn.20....11:initiator> ls
o- iqn.2021-07.192.168.18.11:initiator ............................................................................ [Mapped LUNs: 1]
  o- mapped_lun0 ......................................................................................... [lun0 fileio/disk01 (rw)]
/iscsi/iqn.20....11:initiator> ls /
o- / ......................................................................................................................... [...]
  o- backstores .............................................................................................................. [...]
  | o- block .................................................................................................. [Storage Objects: 0]
  | o- fileio ................................................................................................. [Storage Objects: 1]
  | | o- disk01 .................................................... [/var/lib/iscsi_disks/disk01.img (5.0GiB) write-back activated]
  | |   o- alua ................................................................................................... [ALUA Groups: 1]
  | |     o- default_tg_pt_gp ....................................................................... [ALUA state: Active/optimized]
  | o- pscsi .................................................................................................. [Storage Objects: 0]
  | o- ramdisk ................................................................................................ [Storage Objects: 0]
  o- iscsi ............................................................................................................ [Targets: 1]
  | o- iqn.2021-07.192.168.18.2:target ................................................................................... [TPGs: 1]
  |   o- tpg1 ............................................................................................... [no-gen-acls, no-auth]
  |     o- acls .......................................................................................................... [ACLs: 1]
  |     | o- iqn.2021-07.192.168.18.11:initiator .................................................................. [Mapped LUNs: 1]
  |     |   o- mapped_lun0 ............................................................................... [lun0 fileio/disk01 (rw)]
  |     o- luns .......................................................................................................... [LUNs: 1]
  |     | o- lun0 ............................................. [fileio/disk01 (/var/lib/iscsi_disks/disk01.img) (default_tg_pt_gp)]
  |     o- portals .................................................................................................... [Portals: 1]
  |       o- 0.0.0.0:3260 ..................................................................................................... [OK]
  o- loopback ......................................................................................................... [Targets: 0]
  o- vhost ............................................................................................................ [Targets: 0]
  o- xen-pvscsi ....................................................................................................... [Targets: 0]

ACLを見るとiqn.2021-07.192.168.18.11:initiatorとなっていることが分かるため、clientの認証情報を照らし合わせます。 設定は/etc/iscsi/initiatorname.iscsiにありました。 以下のように変更します。

...
InitiatorName=iqn.2021-07.192.168.18.11:initiator

再度ログインを試みて、ログインに成功することを確認できました。

$ sudo systemctl restart iscsid
$ sudo iscsiadm -m node --login
Logging in to [iface: default, target: iqn.2021-07.192.168.18.2:target, portal: 192.168.18.2,3260] (multiple)
Login to [iface: default, target: iqn.2021-07.192.168.18.2:target, portal: 192.168.18.2,3260] successful.

$ lsblk | grep -E "(NAME|sda)"
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda      8:0    0    5G  0 disk 

8 アドレスが配布できない

IPv6のアドレスがDHCPサーバから上手く設定されない、という問題でした。 (正直動いたからヨシ!という感じで何も理解できていないので後で調査します...)

以下の3つの手順で解決しました。

  1. isc-dhcp-server6が動いていなかったので、configを書いて立ち上げる
  2. RAも広告したいので、radvd を立ち上げる
  3. net.ipv6.conf.all.forwarding = 1 をsetする

isc-dhcp-server6.serviceの起動

まず/etc/defalut/isc-dhcp-serverを編集します。

DHCPDv6_CONF=/etc/dhcp/dhcpd6.conf
DHCPDv6_PID=/var/run/dhcpd6.pid
INTERFACESv6="eth1"

変更した設定を適用します。

$ sudo systemctl enable isc-dhcp-server6
$ sudo systemctl restart isc-dhcp-server6

radvd の起動

さらに、radvdを設定します。

/etc/radvd.conf

interface eth1 {
  AdvSendAdvert on;
  MinRtrAdvInterval 3;
  MaxRtrAdvInterval 10;
  AdvOtherConfigFlag on;

  prefix fc01:1:1:1::/64 {
    AdvOnLink on;
    AdvAutonomous on;
    AdvRouterAddr on;
  };
};

正直設定の意味はあまり分かっていないのですが有効化します。

$ sudo systemctl enable radvd.service
$ sudo systemctl restart radvd.service

net.ipv6.conf.all.forwarding = 1 をsetする

たしかradvd.serviceのstatusを見たら「forwardingしなさい!」みたいなWarningが出ていたため、対応します。

以下の内容で/etc/sysctl.d/99-dhcp-server.confを作成します。

net.ipv6.conf.all.forwarding = 1

これをsudo sysctl -p /etc/sysctl.d/99-dhcp-server.confで適用します。

radvdを再起動してserverの設定は終了です。

あとはclient側で sudo dhclient -6 を実行するとアドレスが降ってきて、pingが通るようになったことを確認できました。

9 止まらない(50点)

フレームワークGinを使って作成されたシンプルなWebサーバをgoで立ち上げるシナリオです。 docker run .... go run main.goみたいな感じで立ち上げられるのですが、Ctrl + Cで止めることが出来ない、という問題を解決する課題でした。

https://docs.docker.com/engine/reference/run/#foreground によると、

A process running as PID 1 inside a container is treated specially by Linux: it ignores any signal with the default action. As a result, the process will not terminate on SIGINT or SIGTERM unless it is coded to do so.

となっており、 Ctrl+Cで終了できない、という問題は、docker runコマンドで起動するプロセスがあらゆるシグナルが無視することが原因だと考えました。

そのため、-it オプションを付与しました。

また、そのままではWebサーバは正常終了することができないため、以下のようにシグナルを待ち受けるよう、(前提条件「host上に追加でパッケージをインストールしてはいけない。」を満たすように)変更を加えました。

diff main.go main.go.bk 
7,10d6
< 
<       "log"
<       "os"
<       "os/signal"
18,42c14
<       server := &http.Server{
<               Addr:    ":8080",
<               Handler: r,
<       }
< 
<       quit := make(chan os.Signal)
<       signal.Notify(quit, os.Interrupt)
< 
<       go func() {
<               <-quit
<               log.Println("receive interrupt signal")
<               if err := server.Close(); err != nil {
<                       log.Fatal("Server Close:", err)
<               }
<       }()
< 
<       if err := server.ListenAndServe(); err != nil {
<               if err == http.ErrServerClosed {
<                       log.Println("Server closed under request")
<               } else {
<                       log.Fatal("Server closed unexpect")
<               }
<       }
< 
<       log.Println("Server exiting")
---
>       r.Run(":8080")

以下のように変更を行うことで、Ctrl+Cで正常終了することが出来ます。

11 キャッシュサーバ立てたけど(250点)

DNSに関する問題です。(多分) キャッシュサーバを立てたのにも関わらずgethostbyname()が実行されてもキャッシュサーバに問い合わせが行われない、という問題でした。

外部DNS(/etc/resolv.confで指定されているリゾルバ)よりsystemd-resolvedが優先させるように変更を加えます。 まず/etc/nsswitch.confhost:で指定されている部分を以下のように書き換えます。

#hosts:      files dns
hosts:      files resolve [!UNAVAIL=return] dns

そしてsystemd-resolvedを再起動することで、終了状態を満たします。

個人的に一番勉強になった問題でした。

12 Webサイトにアクセスできない(200点)

k8sに関する問題です。 Ingressでサービスを公開したが、curl -k https://<loadbalancer IP> とすると502レスポンスが返ってくる、という問題です。

確かまずはじめに、 ingress のログを見ると upstream sent too big header while reading response header from upstream みたいなエラーが出ていることを確認したと記憶しています。 proxy-buffer-sizeが8Bしかない状態だったので、適当な大きさまでproxy-buffer-sizeを上げました。

変更を適用して、再度curlしてみたのですが、今度はhttpsリクエストを待ち受けているはずなのにHTTPリクエストが来ているぞ、というエラーが返ってきました。 ingressからpodへのリクエストがHTTPで飛んでしまっていると見当を付け、HTTPSで振り分けるように変更を加えました。

最終的なdiffは以下のとおりです

$ diff ingress.yaml{,.origin}
7,8c7
<     nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
<     nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
---
>     nginx.ingress.kubernetes.io/proxy-buffer-size: "8"

13 サーバ天気予報 (150点)

CORSに関する問題です。

reverse proxy が web.hogeapi.hoge に振り分けていて、api.hogeにPOSTすると上手く行かない、みたいな問題だったと記憶しています。 apiサーバはExpressで記述されていました。

VNCで接続したPCのブラウザからweb.hogeにアクセスしてみたところ、OPTIONSPOSTでエラーを吐いていそうです。

3点変更を加えました。

  1. リバースプロキシ側のnginxのAccess-Control-Allow-MethodsにPOSTを加える
  2. リバースプロキシのnginxからAccess-Control-Allow-Origin を削除し、api側でapp.use(cors())を有効化する
  3. api側でOPTIONSリクエストを捌くようにする

diffは以下のとおりです。(この辺からメモが雑になる)

user@proxy-server$ diff /etc/nginx/nginx.conf{,.bk}
79c79,80
<             add_header Access-Control-Allow-Methods 'GET,POST';
---
>             add_header Access-Control-Allow-Origin '*';
>             add_header Access-Control-Allow-Methods 'GET';
user@api-server$ diff /srv/api/server.js{,.bk}
6c6
< app.use(cors());
---
> //app.use(cors());
16,20d15
< 
< app.options('/', (req, res) => {
<         res.status(204);
< });
< 

以下のファイルの変更を適用し、それぞれサーバとサービスを立ち上げると上手く行くことが確認できました。 (が、これも「なんか動いたからヨシ!」って感じになっています...)

後はapiサーバを永続化させろということだったので、適当にサービスを作ります。

$ sudo cat <<EOF | sudo tee /etc/systemd/system/ictsc-api.service
[Unit]
Description=ictsc2021-api
After=

[Service]
WorkingDirectory=/srv/api

User=user
Group=user
ExecStart=/usr/bin/npm start
ExecStop=/bin/kill -s QUIT \$MAINPID

Restart   = always
Type      = simple

[Install]
WantedBy=multi-user.target
EOF
$ sudo systemctl daemon-reload
$ sudo systemctl enable ictsc-api.service
$ sudo systemctl start ictsc-api.service
$ sudo systemctl status ictsc-api.service
● ictsc-api.service - ictsc2021-api
     Loaded: loaded (/etc/systemd/system/ictsc-api.service; enabled; vendor preset: enabled)
     Active: active (running) since Sat 2021-08-28 14:53:54 JST; 17s ago
   Main PID: 1665 (npm)
      Tasks: 19 (limit: 1108)
     Memory: 41.1M
     CGroup: /system.slice/ictsc-api.service
             ├─1665 npm
             ├─1686 sh -c node server.js
             └─1687 node server.js

Aug 28 14:53:54 api-server systemd[1]: Started ictsc2021-api.
Aug 28 14:53:55 api-server npm[1665]: > api@1.0.0 start /srv/api
Aug 28 14:53:55 api-server npm[1665]: > node server.js
Aug 28 14:53:55 api-server npm[1687]: server is listening on port 3000...

多分立ち上がっているのでOKです。

14 Webサーバが立ち上がらない(50点)

flaskが使われているプログラムを立ち上げようとすると、 どうやら以前は正常にプログラムが立ち上がっていたらしいのですが、pyenvを導入したことで立ち上がらなくなったようです。

多分pathが変わったのでは?と予想を立てたのですが、まあ強引にpipで入れて動かしました

$ pip3 install flask
$ python3 app/index.py &
 * Serving Flask app 'index' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on all addresses.
   WARNING: This is a development server. Do not use it in a production deployment.
 * Running on http://192.168.7.1:5000/ (Press CTRL+C to quit)
 
$ curl 192.168.7.1:5000 
192.168.7.1 - - [28/Aug/2021 10:13:13] "GET / HTTP/1.1" 200 -
Hello Flask

OKっぽいです。

15 ンジンエックス(150点)

curl --http2 localhost でリクエストを送ったら200レスポンスが返ってくるようにせよ、という問題だった気がします。

以下のようにしてnginxを起動しようとするととエラーが出ました。 どうやらhttp2に関するモジュールが存在していないようです。

$ sudo /usr/local/nginx/sbin/nginx 
nginx: [emerg] the "http2" parameter requires ngx_http_v2_module in /usr/local/nginx/conf/nginx.conf:36

$ sudo /usr/local/nginx/sbin/nginx -V
nginx version: nginx/1.21.1
built by gcc 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04) 
configure arguments: --with-debug
# configure arguments: --prefix=/usr/share/nginx (中略) --with-http_v2_module (後略)となっていないため、サポートされていない

普段ならaptとかで入れ直す、みたいなことが出来ると思うんですが、今回はセキュリティ的な制約でファイルをダウンロードしてはいけない、といった制約があったと記憶しています(もしかしたらもっとマイルドな感じの制約だったかもしれません) nginxのモジュールを導入するにはコンパイルが多分必要だと思うのですが、ファイルをダウンロードしないことにはコンパイルできなさそうだなと考えてしまいました。 (多分想定解はnginxをコンパイルし直すことだったのだろうと思います。そのソースをVM上から探し当てることができませんでした)

そのため、以下のように/usr/local/nginx/conf/nginx.confに変更を加えてお茶を濁しました。(何も解決していない感がすごいですが...)

diff conf/nginx.conf{,.bk}
36c36
<         listen       80;
---
>         listen       443 http2;
$ sudo /usr/local/nginx/sbin/nginx
$ curl --http2 localhost
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

感想

昨年度はほとんど問題で解答にたどり着くことができなかったのですが、今回は沢山問題を解けた気がするので成長を感じました。

非常に教育的に感じる問題も多く、わからない問題を手探りでググりながら学んでいくことができ、色々なことを学ぶ良い機会になったと思います。 非常に楽しいイベントでした。 運営に携わった方々には本当に感謝しています。

いくつかの問題で、想定解と異なる解答をしたり、あるいはよくわからないけど動いている、みたいな状態で提出したりしてしまった部分があったので、改めて勉強し直す必要があるなと感じました。

ただ、久しぶりに長期間集中して作業することになったので、非常に目が疲れました... まずはゆっくり休もうと思います...

BoostnoteでMarkdown上で改行した際にpreview画面でも改行させる

本記事で扱うソフトウェア

  • Boost Note 0.17.1
  • Ubuntu 20.04

目的

多くのMarkdownエディタでは <br> を明示的に挿入してあげないと改行が行われない。 しかし、どうしてもenterキーを押すだけで改行を行いたい。

before
Markdown

abcd
efgh

あいうえお<br>
かきくけこ

preview

abcd efgh

あいうえお
かきくけこ

after
Markdown

abcd
efgh

あいうえお<br>
かきくけこ

preview

abcd
efgh

あいうえお
かきくけこ

方法

カスタムCSSを適用して上げれば目標を達成できることが分かったのでその方法についてメモしておく。

github.com

といっても上記のIssueを見るのが早いのだが。

"File" -> "Preferences" -> "Markdown" にて、p 要素に対して下記のCSSを追加する。

p {
    white-space: pre-wrap;
}

保存すると期待した動作になる。

以上

CyberAgent 2days サーバーサイド向け 開発型インターンシップ ONLINE 参加記

6/6~6/7の2日間でCyberAgentの「2days サーバーサイド向け 開発型インターンシップ ONLINE」に参加させていただきました.

2日間でISUCONのようなことをやっていきました(ISUCONエアプ並感)

具体的には

みたいなことをインターン参加者全員で競うというものです.

実装言語についてはNode.js, PHP, Java, Goから選ぶことが出来ました. 私はどれもそこまで詳しく知りませんでしたが(どれも1ヶ月も触っていない),なんとなくGoを選択しました.
Goの選択者が8割くらいでした.

インターンの中でどのようなことをしたのか,どう考えるべきだったかなどを振り返って行こうと思います.
メモを取りながらやっていたのですが,どうやら最後の最後でメモを他のファイルで上書きしてしまったようで,正しく書けるか心配です...
commitログを見ながら頑張って思い出します.

1日目

作業環境整理

とりあえず作業環境を整えることを目指しました.

  • ssh鍵登録
  • マシンスペック確認
  • zsh,vimの環境構築
  • 使用されているミドルウェアの設定ファイルやアプリケーションなどをhomeディレクトリにcopy
  • go実装に切り替える
  • nginx.confにaccess logを設定
  • ここでベンチを回す
  • MySQLでslow query logを整理
  • deployスクリプトを整備
  • MySQLの中身をdump
  • VSCode-remoteの設定
  • kataribe, pt-query-digest, pprofなどのインストール

を,大体上から順に行っていきました.

今思い返せば,

  • ベンチを回す
  • homeディレクトリにとりあえず全部移してはじめからgit管理する
  • deployごとにgit commitが走るようにする

などを行えていたらよかったと反省しています(ぶっ壊したときの復旧にめちゃくちゃ時間を掛けてしまったため)

これらを全部終えられたのが大体2時間くらいだと思います.

deployサイクルとしては,

  • homeディレクトリに存在するconfigファイルやアプリケーションをいじる
  • deploy.shを走らせる
  • プロファイルする

という感じなものを目指していたため,deploy.shの中で,

  • slow queryログ,access logのrotate
  • app.goのmakeと,サービスが動作していたディレクトリへのコピーとバックアップ

の2つをやりました.これもプロファイル情報を加工してすぐに簡単に見えるようにする工夫をすればよかったのかなと思います.

あとは,N+1あるぞ,と思って改善したり,COUNT(*)が色んなとこで呼ばれてて遅そうだな?とか思ってゴニョゴニョしていましたが,全くスコアは上昇しませんでした.
後で原因は判明したのですが,deploy.shを走らせたときに正しくデプロイされていなかったのが原因です.
「スコアが上がったときにcommitすればいいか〜」などと楽観的に考えていたため,あとで絶望することになりました.こういう人為的なミスを引き起こしそうなところはどんどんスクリプトに落としていくべきだったと後悔しています.

中間説明

中間説明では,

  1. プロファイラの設定
  2. 最初のボトルネック特定
  3. ボトルネック解消

のサイクルを一通り簡単に説明していただきました.
それを元にボトルネックを解消することにしました.
正直中間説明前までは何も改善作業をできておらず,スコアも最下位でした.プロファイラを見て何処のエンドポイントが悪さをしているのかがわかっても,何が悪さをしているのかが分からず途方に暮れていたので,中間説明に非常に感謝しています:bow:

indexを張る

2つくらい確かindexを張るところがあったので張りました.

GET / を改善しようと頑張る

めっちゃ重いクエリがgetPopularArticle()の中にあることが分かり,Countの結果でsortしているためこのクエリのまま改善することは難しそうだと分かりました.
とりあえず,articlesテーブルの中にカウンタを用意しておき,その値を読むようにしようと変更を試みましたが,実装が重くてこれでめちゃくちゃ時間を食ってしまいました.

1日目終了

以上で1日目に作業した作業は終わりです. 今振り返ってみると,この時点ではDBの変更しかスコアに反映されていないため,どの言語でやっても同じくらいのスコアだった感がありますね. (一応Goのコードも触っていたのですが,デプロイされていないためスコアには反映されていません)

2日目

N+1の改善

getUser

GET / は諦めて,他のプロファイルを見てみます. articlesが遅いというのがでてきたので,そこを見てみます. すると,getUserでauthorを取得した後にauthorが書いたarticleのupdated_atを取得するクエリをN回叩いていそうです. そこを改善しました. テーブルを結合してそのまま突っ込んであげれば良さそうでした.

( {{hogeFunc}} みたいにevalっぽい動きをするやつは,<!-- -->で囲ってコメントアウトしても呼ばれてしまうことに気づかずにベンチを回してしまい,スコアが伸びなくて焦りました)

getArticleTagNames

これもN+1でした. LEFT JOIN してあげればスコアが少しだけ上がりました.

再び GET / を改善しようと頑張る

さて,やはり GET / が遅いということで,slow queryを確認して見ると, getPopularArticle()が重いことが分かります. カウンタの実装は流石にきつそうだったので,どうしようかと思っていたところ, チームメンバーに「メモリ全然使われていないから,それを使うといいよ」というアドバイスを頂いたので,オンメモリで動かしてみようと思いました.

1回目

まずはじめに,DBへのアクセスを減らせばいいよな,と思い,iine_drity_flagのようなものを用意しました. これは,iineが作成,削除などを行われたことを管理するもので,flagがtrueであるときにgetPopularArticle()が呼ばれた時にだけDBにアクセスするようにしました. これにより,スコアが劇的に改善されました.(2000点台にまでスコアが伸ばせました)

しかしプロファイラはまだDBアクセスが律速だと言っています.

2回目

dirty_flagを消し去り,全てのiineCntをメモリで管理することを試みました.
具体的には,初期状態のcntをapp起動時にmap[article_id] = iineCntの形で初期化しておき,iineに変更が加えられたときに値を上下させ,sortさせることを考えました.
ただ,うまく初期化する方法が思いつかず,残り時間があと僅かだったため,この作業を停止しました.

最後のベンチマークを走らせる

以前のコミットに戻ってプロファイラを全て切ってVSCode-remoteも切ってスコアを計算しようとしました.ここで大ポカをやらかします. N+1を改善したときのcommit以降コミットを忘れていたせいで,スコアが大幅に落ちます. おそらく,getUser, getArticleTagNames, iine_dirty_flagの全ての作業を消し飛ばしてしまったのだと思います. iine_dirty_flagの作業は覚えていたためそれは復旧させましたが,getUser, getArticleTagNamesの変更を消し飛ばしてしまったことに気づいたのはベンチマークサーバ停止後でした.

最終スコア

  • index修正✕2
  • getPopularArticle()の改善
  • (何か他に忘れている作業)

のスコアで1885でした.

f:id:AkkyOrz:20200608154500p:plain
最終スコア

ただ,奇跡的に過去のスコアが残っていたため,スコアは2121でした.

f:id:AkkyOrz:20200608161438p:plain
最高スコア
(スコアサーバはインターン終了後に少しの間動かしておくということだったので,最終スコアは別の値となっています.)

その後

結果発表後,解説を聞いたり,社員さんのパネルディスカッションを聞いたり,社員さんと直接お話をしたりしました. それぞれで一番面白かったのは以下のとおりです.

  • スコアを上げるにはいくつか壁があり,高い点数を目指すならHandler側からHTMLファイルを生成して埋め込むなどの手法があるという話
  • ISUCON優勝者の方がプライベートクラウドの開発をしてるよ〜というお話
  • サービスインの前は非常に気を使って負荷テストを行っているという話(ゲーム開発の方)

最後に

2000点台に乗せることができてとても嬉しかったです. 全体では5位ということでしたが,同チームの人のアドバイスやメンターの方のアドバイスのおかげだと思います. 基本的に初期ではDBに律速が生じるため,まずはINDEXを確認したり,クエリを確認したりしないと,大きな律速を解除することができずスコアが伸びないことが分かりました. 計測→改善というパフォーマンスチューニングの姿勢に少しは身につけられたではないかなと思います.非常に有意義なインターンでした.

(P.S. MacWindowsが推奨されていましたが,Linuxマシンでも全然大丈夫そうでした.)

Chromeで特定サイトへのアクセスのみをproxy server経由で行う方法

はじめに

これは筆者が学内のIPアドレスのままリモートで論文にアクセスする方法を模索した結果をメモしたものです.
大学公認のSSL-VPN Gatewayサービスを使えと言う意見はごもっともなのですが,なんとなく不便を感じたため,他の方法を模索しました.
色々と模索した結果,ユーザ側で自由度の高そうなアクセス処理ができそうな方法を見つけたので共有しようと思った次第です.
筆者はpaperpileを論文管理に用いているため,Chromeをブラウザとして利用していますが,Firefoxなどでも可能だと思います.

設定の方法のみを述べるため,SOCKS proxyやDynamic Forwardingの説明,sshの細かい設定の仕方などには触れません. 既にいくつかのサイトにて説明がされているため,他のサイトをご参照ください.

何か他に良い方法をご存知の方は,コメントしていただけると幸いです.

対象者 and 目的

  • VPNサーバを(何らかの理由で)立てることはできないが,sshでアクセスは可能という場合
  • HTTP(or HTTPS)で特定のサイトに踏み台サーバのIPアドレスでアクセスしたい場合
  • 全てのトラフィックVPNに流してトラフィックを増大・集中させたくない場合
  • 特定サイト以外はダイレクトにアクセスしたい場合
  • SwitchyOmegaの使い方を知りたい場合

使用するツール

構成図

Create a SOCKS proxy on a Linux server with SSH to bypass content filters を参考にしました.

f:id:AkkyOrz:20200509233446j:plain
ネットワーク的な構成図

設定方法

大きく分けると,以下の2つを行う必要があります.

  1. sshでトンネルを張る
  2. SwitchyOmegaを設定する

1. sshでトンネルを張る

~/.ssh/configを設定します.

第617回 SOCKSを利用してSSHのみで簡易VPNを構築する:Ubuntu Weekly Recipe|gihyo.jp … 技術評論社 を参考に設定します.

Host socks
    HostName <proxy server name>
    User <username>
    IdentityFile /path/to/secret_key
    DynamicForward 1080
    Compression yes
    ExitOnForwardFailure yes
    Port <ssh port (default 22)>

その後ターミナルを起動し,

$ ssh socks -f

でトンネルを張ります.-fオプションはバックグラウンド実行のオプションです.

2. SwitchyOmega を設定する

Chrome Extensionを追加

Proxy SwitchyOmega - Chrome Web Store から,SwitcyOmegaをChromeに追加します.

次に,SwitchyOmega の設定を行います.

qiita.com を参考にしました.

Chrome Extension を追加した後,アイコンをクリックし,Options を選択します.

これはSwitchyOmegaの画像です.
SwitchyOmegaのアイコン

Proxy Profile の作成

まずは Proxy の設定をします. SwitchyOmega のオプション画面が開いたら,左の欄の PROFILES から New Profile を選択します.

f:id:AkkyOrz:20200513222741p:plain
SwitchyOmegaのNewProfile選択画面

Profile name を入力し, Proxy Profile を選択して Create ボタンを押します.

f:id:AkkyOrz:20200513223416p:plain
New Profile作成画面

Proxy の設定画面が開いたら,以下の図のように Proxy を設定します.
この設定画面では,利用するProxyのプロトコル,サーバのIPアドレス,Port番号を指定します.
今回はローカルの1080番ポートにポートフォワーディング用のポートを開いているため,localhost:1080にSOCKS5で接続します.
設定したら,左の欄の ACTIONS から Apply changes を押して変更を反映させます.

f:id:AkkyOrz:20200513223547p:plain
Proxy設定画面

Switch Profile の作成

次に,Switch の設定をします.
先ほどと同様に,左の欄の PROFILES から New Profile を選択し今度は Switch Profile を作成します.
Profile name を入力し,Switch Profile を選択して Create ボタンを押します.

Condition Type, Condition Details, Profile を組みあわせて設定を行います.
上から下に向けてマッチングを行っていくため,上の方設定のほうが優先されます.
詳しい設定の方法については,下図のマウスカーソルの部分の?をクリックしてでてくるHelpを参照してください.
私はProxy経由でアクセスしたいサイトに 先程作成したProfileを設定し,それに当てはまらないサイトは直接接続するように設定しました.

f:id:AkkyOrz:20200513234849p:plain
Switch設定画面

Apply changes を選択して設定を反映させます.

Switchの有効化

最後にSwitchの設定を有効にします.
SwitchyOmegaのアイコンをクリックし,使用したいSwitchのProfileをクリックして有効化します.
これで設定は完了です.

まとめ

VPNではなく,SOCKS proxy を用いて特定のサイトにアクセスする方法についてまとめました.
参考になれば幸いです.

備考

Firefoxでの利用方法

github.com でも言及されていますが,SwitchyOmegaには,Experimentalなアドオンが存在するようです.
これを利用するとChromeと同様なことができると思います.

結局コマンド実行してるし,結局ログインする手間と変わらなくない?WOWWOW?

ごもっともです.

それって大学のネットワークでやっていいの?

東京大学附属図書館 ASKサービス - 詳細画面 を見て,研究室内のIPアドレスを用いて論文をダウンロードすることはOKだと判断しました. ただ,大量にダウンロードを行うとシステマティックダウンロードと疑われてしまうため,注意が必要とのことでした.

Warningについて

導入してすぐに,いくつかのWarningっぽいのがでました.

Resources that failed to load A few resources failed to load due to issues with your network, proxy server or the webpage. SwitchyOmega is just the reporter of these issues, not the cause of them.

1 .fontawesome.com 1 .bootstrapcdn.com

You can add switch conditions for them only when using a Switch Profile.

firefox/SwitchyOmega.sorl at master · nonoroazoro/firefox みたいなのを導入するといいのかもしれません.

Ubuntu Weekly Recipe

今回のSOCKS Proxyの詳しい内容は,技術評論社が連載を行っている,Ubuntu Weekly Recipe を参照しています.

gihyo.jp

今回のSOCKS Proxyの話以外にも,参考になる記事が沢山有るので,Ubuntuを使用している方は特に見てみると良いかもしれません.おすすめです.