LoginSignup
5
5

More than 3 years have passed since last update.

Docker Compose Limitsを実行する際の生産性のヒントとベストプラクティス - Docker Composeのための実践的なエクササイズ その4

Posted at

このチュートリアルでは、Alibaba Cloud上でコンテナを扱う際にDocker Composeを使用して実践的な経験を積むことに焦点を当てています。

本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。

生産性のヒント

私はよく使うDockerコマンド用にいくつかのbash aliasesを定義しています。ここではそのうちの2つを紹介します。

    alias nnc='nano docker-compose.yml'
    alias psa='docker ps -a'

シェルで psa を入力すると、チュートリアルで docker ps -a をハイライトしてからコピーし、alt-tab でコンソールウィンドウに移動してからペーストするよりも速いです。

docker-compose.yml の編集が速くなりました。

1、Chromeを使用している場合は、オートコピー拡張機能をインストールしてください。ブラウザでハイライトしたテキストを自動的にコピーしてくれます。
2、チュートリアルでdocker-compose.ymlのテキストをハイライト表示します。
3、AltタブでLinuxコンソールへ
4、type nnc ( editorで docker-compose.yml ファイルを開きます )
5、右クリックして貼り付けます(これは使用しているコンソールソフトウェア固有のものです)。
6、保存
拡張子とエイリアスを使用しない場合、このプロセスには上記の6つのステップの代わりに12のステップが必要になります。

シェルで psa を入力するのは、チュートリアルで docker ps -a をハイライトしてからコピーし、alt-tab でコンソールウィンドウに移動してからペーストするよりも速いです。

docker-compose.yml の編集が速くなりました。

1、Chromeを使っている場合は、オートコピー拡張機能をインストールしてください。ブラウザでハイライトしたテキストを自動的にコピーします。
2、チュートリアルでdocker-compose.ymlのテキストをハイライト表示します。
3、AltタブでLinuxコンソールへ
4、type nnc ( エディタが docker-compose.yml ファイルを開く )
5、右クリックして貼り付けます(これは使用しているコンソールソフトウェアに固有のものです)。
6、保存
拡張子とエイリアスを使用しない場合、このプロセスには上記の6つのステップの代わりに12のステップが必要になります。

Deploy: placement constraints

docker-composeの配置制約は、制約を定義することで、タスクをスケジューリング/実行できるノード/サーバを制限するために使用されます。

まず、ノード/サーバのラベルを定義する必要があります。次に、これらのラベルに基づいて配置制約を定義します。

ノードにラベルを追加する構文

docker node update --label-add label-name=label-value hostnam-of-node

このためにはサーバのホスト名が必要です。シェルでホスト名を入力して、あなたのホスト名を取得します。

以下、自分のホスト名を使ってください。( localhost.localdomain = 私のホスト名)

docker node update --label-add tuts-allowed=yes localhost.localdomain

docker node update --label-add has-ssd=yes localhost.localdomain

ノードを検査してラベルが存在することを確認することができます。

head -n 13 は先頭のみを表示します。 / head 13行の長いinspectの出力。

docker node inspect self | head -n 13

期待される出力 .

[
    {
        "ID": "wpk3r9ypjd8f0p3koh1dikvie",
        "Version": {
            "Index": 443
        },
        "CreatedAt": "2018-11-06T09:29:29.644400514Z",
        "UpdatedAt": "2018-11-07T09:55:18.065758325Z",
        "Spec": {
            "Labels": {
                "has-ssd": "yes",
                "tuts-allowed": "yes"
            },

ここで、docker-compose.yml の一番下に配置規則を追加します。

docker-compose.yml に次のように追加します。

nano docker-compose.yml
version: "3.7"
services:
  alpine:
    image: alpine:3.8
    command: sleep 600

    deploy:
      placement:
        constraints:
          - node.labels.has-ssd == yes
          - node.labels.tuts-allowed == yes

stack deployコマンドは、両方の制約に一致するノードにのみサービススタックを配置します。

両方の制約が私たちのノードと一致しているので、デプロイは成功します。

docker stack deploy -c docker-compose.yml  mystack

実行中のコンテナをリストアップしてみましょう。

docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
42f8a9b43faf        alpine:3.8          "sleep 600"         12 seconds ago      Up 11 seconds                           mystack_alpine.1.ezwejfbrmhbk0k5yd9a53ei85

成功しました。mystackにある全てのサービスをリストアップしてみましょう。

docker stack services mystack

ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
ji5dnn9klinx        mystack_alpine      replicated          1/1                 alpine:3.8  

成功しました。REPLICAS欄を参照してください。リクエストされた1つのサービスのうち、1つのサービスが実行されています。

それでは、デプロイができないように制約テストを変更してみましょう。

docker-compose.ymlに以下を追加します。

nano docker-compose.yml
version: "3.7"
services:
  alpine:
    image: alpine:3.8
    command: sleep 600

    deploy:
      placement:
        constraints:
          - node.labels.has-ssd == yeszzz
          - node.labels.tuts-allowed == yeszzz

以前に展開したスタックを削除します。

docker stack rm mystack
docker stack deploy -c docker-compose.yml  mystack

mystackのサービスを一覧にしてみましょう。

docker stack services mystack
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
jdg2bgxx5nfa        mystack_alpine      replicated          0/1                 alpine:3.8   

デプロイは適切なノードを見つけることに成功しませんでした。REPLICAS カラムを参照してください。要求された 1 つのサービスのうち 0 つのサービスが実行されています。

ノードラベルの使用方法の例。

1、ssds を必要とする特定のアプリがそのようなノードでのみ実行できるように ssds でノードをラベル付けします。
2、アプリケーションがそのようなノードを見つけることができるように、グラフィックカードでノードをラベル付けします。
3、必要に応じて国名、州名、都市名をノードにラベル付けします。
4、バッチとリアルタイムのアプリケーションを分離します。
5、開発ジョブは開発用マシンでのみ実行してください。
6、開発中のアプリケーションを物理的なコンピュータ上でのみ実行してください - CPUやRAMを浪費するコードが同僚に悪影響を与えないようにしてください。
Placementeについての詳しい情報はこちらをご覧ください: https://docs.docker.com/compose/compose-file/#placement

制約についてはこちらをご覧ください: https://docs.docker.com/engine/reference/commandline/service_create/#specify-service-constraints-constraint

Deploy: replicas

任意の時間に実行するコンテナの数を指定します。

これまではデフォルトのreplicasを1つだけ使用していました。

ここでは3つのreplicasを実行するデモをしてみましょう。

docker-compose.ymlに以下のように追加します。

nano docker-compose.yml
version: "3.7"
services:
  alpine:
    image: alpine:3.8
    command: sleep 600

    deploy:
      replicas: 3

以前に実行していたmystackの削除

docker stack rm mystack

期待される出力 .

Removing service mystack_alpine
Removing network mystack_default

新しいスタックを展開します。

docker stack deploy -c docker-compose.yml  mystack

期待される出力 .

Creating network mystack_default
Creating service mystack_alpine

出力は期待できそうにありません - 3つのreplicasについては言及されていません。mystackのサービスをリストアップしてみましょう

docker stack services mystack

期待される出力 .

ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
pv2ebn95au9j        mystack_alpine      replicated          3/3                 alpine:3.8   

要求された3つのレプリカが不足しています。成功しました。

実行中のコンテナをリストアップしてみましょう。

docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
80de65e6e0d3        alpine:3.8          "sleep 600"         9 seconds ago       Up 6 seconds                            mystack_alpine.2.51hlsv3s7ky5zr02fprjaxi59
440548cfcc7d        alpine:3.8          "sleep 600"         9 seconds ago       Up 6 seconds                            mystack_alpine.1.za68nt6704xobu2cxbz7x7p3l
19564317375f        alpine:3.8          "sleep 600"         9 seconds ago       Up 7 seconds                            mystack_alpine.3.1ut387z38e2hrlmahalp7hfsa

予想通りです。3つのコンテナが稼働しています。

サーバの作業負荷を調査してみました。

top - 09:45:33 up  2:02,  2 users,  load average: 0.00, 0.01, 0.05
Tasks: 133 total,   1 running, 132 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.3 us,  0.2 sy,  0.0 ni, 99.5 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :  985.219 total,  472.004 free,  171.848 used,  341.367 buff/cache
MiB Swap: 1499.996 total, 1499.996 free,    0.000 used.  639.379 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
  939 root      20   0  950.7m 105.9m  29.2m S   0.7 10.8   1:19.36 dockerd
  946 root      20   0  424.8m  29.1m  12.1m S        2.9   0:14.14 docker-containe
 5082 root      20   0    7.2m   2.7m   2.0m S        0.3           docker-containe
 4950 root      20   0    7.2m   2.6m   2.0m S        0.3           docker-containe
 5075 root      20   0    7.2m   2.4m   1.9m S        0.2           docker-containe

3つの小さなコンテナはそれぞれ約2.5MBのRAMを使用しています。

これで、隔離された環境で動作する3つのフルAlpine Linuxディストロが、それぞれ2.5MBのRAMサイズで動作するようになりました。これは素晴らしいことです。

これを3つの独立した仮想マシンと比較してみてください。それぞれの仮想マシンは、存在するだけで50MBのオーバーヘッドを必要とします。さらに、それぞれに数百 MB のディスクスペースが必要になります。

各コンテナは約300msで起動しますが、これはVMでは不可能です。

Deploy: resources: reservations cpu ( オーバープロビジョニング )

reservations: cpu config settingsを使ってCPU容量を確保しています。

cpuをオーバープロビジョニングして、Dockerが指示に従うかどうかを確認してみましょう。

docker-compose.ymlに以下を追加します。

nano docker-compose.yml
version: "3.7"
services:
  alpine:
    image: alpine:3.8
    command: sleep 600

    deploy:
      replicas: 6

      resources:
        reservations:
          cpus: '0.5'

私のサーバには 2 つのコアがあるので、2 つの CPU が利用可能です。

この設定では、6 * .5 = 3CPUをプロビジョニングしようとしています。

この設定を編集して、あなたの小さなノートパソコンや、あなたの雇い主であるスーパーサーバーで失敗するようにしなければなりません。

既存のスタックを削除してみましょう。

docker stack rm mystack

展開してみましょう。

docker stack deploy -c docker-compose.yml  mystack

期待されない出力 .

Creating service mystack_alpine
failed to create service mystack_alpine: Error response from daemon: network mystack_default not found

時々上記のようなことが起こりますが、エラーが出なくなるまでデプロイを再実行してください。

デプロイの結果を調査してください。

docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
a8861c79e3e8        alpine:3.8          "sleep 600"         40 seconds ago      Up 37 seconds                           mystack_alpine.6.pgtvnbpxpy4ony26pmp8ekdv1
3d2a0b8e52e9        alpine:3.8          "sleep 600"         40 seconds ago      Up 37 seconds                           mystack_alpine.5.j52dwbe7qqx0nn5nhanp742n4
5c2674b7fa36        alpine:3.8          "sleep 600"         40 seconds ago      Up 37 seconds                           mystack_alpine.1.bb1ocs3zkz730rp9bpf6s9jux
f984a8d52393        alpine:3.8          "sleep 600"         40 seconds ago      Up 38 seconds                           mystack_alpine.4.mr5ktkei9pn1dzhkggq2e48o9

4つのコンテナがリストアップされています。4 * .5 = 2CPUを使用していることになります。

mystackにある全てのサービスをリストアップする。

docker stack services mystack
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
7030g8ila28h        mystack_alpine      replicated          4/6                 alpine:3.8   

6つのコンテナのうち4つだけがプロビジョニングされました。Dockerはプロビジョニングするcpusを使い果たしました。

重要: これは存在するものだけをreservationすることができます。

Deploy: resources: reservations: RAM( オーバープロビジョニング )

RAMをオーバープロビジョニングしてみましょう。(この機能はこのチュートリアルの後半で正しく使用します。)

docker-compose.yml に以下を追加します。

nano docker-compose.yml
version: "3.7"
services:
  alpine:
    image: alpine:3.8
    command: sleep 600

    deploy:
      replicas: 6

      resources:
          memory: 2000M

私のサーバーには1GBのラムがあります。

この設定では、私は6 * 2 = 12 MBをプロビジョニングしようとしています。

前と同じように: この設定を編集して、あなたの小さなノートパソコンやモンスターの雇用主のスーパーサーバーで失敗するようにする必要があります。

既存のスタックを削除してみましょう。

docker stack rm mystack

展開してみましょう。

docker stack deploy -c docker-compose.yml  mystack

実行中のstack servicesを一覧表示します。

docker stack services mystack

期待される出力 .

ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
l7yr2m5k6edf        mystack_alpine      replicated          0/6                 alpine:3.8   

デプロイされたサービスはゼロ。Dockerは、指定された予約RAMの50%を使用して1つのコンテナをデプロイすることさえしません。これは正しく想定しています - RAMの予約を指定した場合、コンテナが正常に動作するためにはその最小値を必要とします。したがって、予約が不可能な場合、コンテナは起動しません。

ここまでで、リソースの制限が守られることを見てきました。

どのように動作するかを確認するために、妥当な制限を定義してみましょう。

Deploy: resources: limits: cpu

以下のアルパインサービスは、20M以下のメモリと0.50(50%)以下の利用可能な処理時間(CPU)に制約されています。

docker-compose.ymlに以下を追加します。

nano docker-compose.yml  
version: "3.7"
services:
  alpine:
    image: alpine:3.8
    command: sleep 600

    deploy:
      replicas: 1

      resources:
        limits:
          cpus: '0.5'
          memory: 20M

ここから先はreplicasが1つしか必要ないことに注意してください。

docker stack rm mystack

スタックを展開します。

docker stack deploy -c docker-compose.yml  mystack
docker ps -a

期待される出力 .

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                       PORTS               NAMES
11b8e8c2838b        alpine:3.8          "sleep 600"         3 seconds ago       Up 1 second                                      mystack_alpine.1.qsamffjd1vg0137off9xinyzg

コンテナが動いています。これを入力して、CPU速度をベンチマークしてみましょう。

docker exec -it mystack_alpine.1.qsamffjd1vg0137off9xinyzg /bin/sh

コンテナ / # プロンプトに表示されているコマンドを入力します。

/ #  time dd if=/dev/urandom bs=1M count=2 | md5sum
2+0 records in
2+0 records out
real    0m 0.57s
user    0m 0.00s
sys     0m 0.28s
064be3476682daf856bb32fa00d29e2e  -
/ # exit

ベンチマークの説明:

1、time: 経過時間を測定します。
2、dd if=/dev/urandom bs=1M count=2: 1 MB のランダム性を bs (ブロックサイズ) を 2 回コピーする。
3、md5sum: md5ハッシュを計算する (CPUに負荷を与える)
CPU 制限値 0.5 のベンチタイムがありますが、これを比較できなければ何の意味もありません。

そこで、docker-compose.yml で cpu limit を 0.25 に変更してみましょう。

cpus: '0.25'

その後、シェルで実行します。

docker stack rm mystack
docker stack deploy -c docker-compose.yml  mystack
docker ps -a # to get our container name 
docker exec -it mystack_alpine.1.cbaakbi027ue0c1rtj0z463qz /bin/sh

ベンマークを再実行します。

/ # time dd if=/dev/urandom bs=1M count=2 | md5sum
2+0 records in
2+0 records out
real    0m 1.27s
user    0m 0.00s6d9b25e860ebef038daa165ae491c965  -
sys     0m 0.30s
/ # time dd if=/dev/urandom bs=1M count=2 | md5sum
2+0 records in
2+0 records out
real    0m 1.33s
ed29ebf0ef70923f9b980c65495767eb  -
user    0m 0.00s
sys     0m 0.33s
/ # exit

結果は理にかなっています。 - 約50%の速度低下 - 利用可能なCPUパワーが50%減少しています。

そこで、docker-compose.ymlでCPU制限を1.00に変更してみましょう。

     cpus: '1.00'

シェルで実行します。

docker stack rm mystack

docker stack deploy -c docker-compose.yml  mystack

docker ps -a

docker exec -it your-container-name /bin/sh

表示されているコマンドを入力します。

/ # time dd if=/dev/urandom bs=1M count=2 | md5sum
2+0 records in
2+0 records out
real    0m 0.25s
user    0m 0.00s
sys     0m 0.24s
facbf070f7328db3321ddffca3c4239e  -
/ # time dd if=/dev/urandom bs=1M count=2 | md5sum
2+0 records in
2+0 records out
616ba74d54b8a176f559f41b224bc3a3  -real 0m 0.29s
user    0m 0.00s
sys     0m 0.28s
/ # exit

非常に高速なランタイム:100%のCPU制限は25%のCPUパワーよりも4倍速い

これで、コンテナごとのCPUパワーを制限することが期待通りに動作することを体験していただけたと思います。

本番用のサーバーが 1 台しかない場合、この知識を使って、CPU を消費するバッチプロセスを他の作業と同じサーバーで実行することができます - バッチプロセスの CPU を厳しく制限するだけです。

Resources: limits: memory

この設定オプションは、コンテナの最大 RAM 使用量を制限します。

docker-compose.yml に次のように追加します。

nano docker-compose.yml 
Version: "3.7"
services:
  alpine:
    image: alpine:3.8
    command: sleep 600

    deploy:
      replicas: 1

      resources:
        limits:
          cpus: '1.00'
          memory: 4M

実行します。

docker stack rm mystack

docker stack deploy -c docker-compose.yml  mystack

docker ps -a

期待される出力 .

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
2e0105ce94fd        alpine:3.8          "sleep 600"         3 seconds ago       Up 1 second                             mystack_alpine.1.ykn9fdmeaudp4ezar7ev19111

これで、メモリ制限が4MBのコンテナが稼働していることになります。

典型的な好奇心旺盛なDocker管理者になって、8MBの/dev/shm RAMを使用した場合にどうなるか見てみましょう。

docker exec -it mystack_alpine.1.ykn9fdmeaudp4ezar7ev19111 /bin/sh

表示されているようにコマンドを入力します。

/ # df -h
Filesystem                Size      Used Available Use% Mounted on
/dev/mapper/docker-253:1-388628-c88293aae6b79e197118527c00d64fee14aec2acfb49e5f1ec95bc6af6bd874b
                         10.0G     37.3M     10.0G   0% /
tmpfs                    64.0M         0     64.0M   0% /dev
tmpfs                   492.6M         0    492.6M   0% /sys/fs/cgroup
/dev/mapper/centos00-root
                         12.6G      5.5G      7.2G  43% /etc/resolv.conf
/dev/mapper/centos00-root
                         12.6G      5.5G      7.2G  43% /etc/hostname
/dev/mapper/centos00-root
                         12.6G      5.5G      7.2G  43% /etc/hosts
shm                      64.0M         0     64.0M   0% /dev/shm
tmpfs                   492.6M         0    492.6M   0% /proc/acpi
tmpfs                    64.0M         0     64.0M   0% /proc/kcore
tmpfs                    64.0M         0     64.0M   0% /proc/keys
tmpfs                    64.0M         0     64.0M   0% /proc/timer_list
tmpfs                    64.0M         0     64.0M   0% /proc/timer_stats
tmpfs                    64.0M         0     64.0M   0% /proc/sched_debug
tmpfs                   492.6M         0    492.6M   0% /proc/scsi
tmpfs                   492.6M         0    492.6M   0% /sys/firmware
/ # dd if=/dev/zero of=/dev/shm/fill bs=1M count=4
4+0 records in
4+0 records out
/ # df -h
Filesystem                Size      Used Available Use% Mounted on
/dev/mapper/docker-253:1-388628-c88293aae6b79e197118527c00d64fee14aec2acfb49e5f1ec95bc6af6bd874b
                         10.0G     37.3M     10.0G   0% /
tmpfs                    64.0M         0     64.0M   0% /dev
tmpfs                   492.6M         0    492.6M   0% /sys/fs/cgroup
/dev/mapper/centos00-root
                         12.6G      5.5G      7.2G  43% /etc/resolv.conf
/dev/mapper/centos00-root
                         12.6G      5.5G      7.2G  43% /etc/hostname
/dev/mapper/centos00-root
                         12.6G      5.5G      7.2G  43% /etc/hosts
shm                      64.0M      4.0M     60.0M   6% /dev/shm
tmpfs                   492.6M         0    492.6M   0% /proc/acpi
tmpfs                    64.0M         0     64.0M   0% /proc/kcore
tmpfs                    64.0M         0     64.0M   0% /proc/keys
tmpfs                    64.0M         0     64.0M   0% /proc/timer_list
tmpfs                    64.0M         0     64.0M   0% /proc/timer_stats
tmpfs                    64.0M         0     64.0M   0% /proc/sched_debug
tmpfs                   492.6M         0    492.6M   0% /proc/scsi
tmpfs                   492.6M         0    492.6M   0% /sys/firmware


/ # dd if=/dev/zero of=/dev/shm/fill bs=1M count=8
Killed


/ # df -h
Filesystem                Size      Used Available Use% Mounted on
/dev/mapper/docker-253:1-388628-c88293aae6b79e197118527c00d64fee14aec2acfb49e5f1ec95bc6af6bd874b
                         10.0G     37.3M     10.0G   0% /
tmpfs                    64.0M         0     64.0M   0% /dev
tmpfs                   492.6M         0    492.6M   0% /sys/fs/cgroup
/dev/mapper/centos00-root
                         12.6G      5.5G      7.2G  43% /etc/resolv.conf
/dev/mapper/centos00-root
                         12.6G      5.5G      7.2G  43% /etc/hostname
/dev/mapper/centos00-root
                         12.6G      5.5G      7.2G  43% /etc/hosts
shm                      64.0M      5.4M     58.6M   9% /dev/shm
tmpfs                   492.6M         0    492.6M   0% /proc/acpi
tmpfs                    64.0M         0     64.0M   0% /proc/kcore
tmpfs                    64.0M         0     64.0M   0% /proc/keys
tmpfs                    64.0M         0     64.0M   0% /proc/timer_list
tmpfs                    64.0M         0     64.0M   0% /proc/timer_stats
tmpfs                    64.0M         0     64.0M   0% /proc/sched_debug
tmpfs                   492.6M         0    492.6M   0% /proc/scsi
tmpfs                   492.6M         0    492.6M   0% /sys/firmware
/ # exit

上で起こったことの説明。

最初に df -h を実行して /dev/shm のサイズと使用量を調べます。

shm                      64.0M         0     64.0M   0% /dev/shm

そして、/dev/shmに4MBを追加します。

dd if=/dev/zero of=/dev/shm/fill bs=1M count=4

使用方法を再確認してください - 4Mを参照してください。

shm                      64.0M      4.0M     60.0M   6% /dev/shm

そして、/dev/shm に 8MB を追加して、以前の内容を上書きします。

dd if=/dev/zero of=/dev/shm/fill bs=1M count=8
Killed

で、このコマンドは強制終了されます。

dev/shm の使用状況を再度確認してください。

shm                      64.0M      5.4M     58.6M   9% /dev/shm

コンテナがRAMを使い果たす前に4MBを少し使いました。

結論:Dockerのdocker-composeのメモリ制限が適用されます。

デフォルトではコンテナはRAMの使用量が制限されていません。そのため、このリソース制限を利用して、暴走したコンテナによってRAMが完全に消費されるのを防ぎましょう。

20MB、50MB、100MBのいずれも240GBを消費させるよりは良いでしょう。

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5