Photoruction工事中!

Photoructionの開発ブログです!

自分のリバースSSHトンネルサーバーを立てましょう

背景

開発者として、間違いなくローカルで環境を立ち上げて開発するのは一番楽です。

ただし、localhost で開発するには限界がある。

  • localhost と言うドメインはブラウザとシステムに特別な扱いされている
    • もちろん hosts ファイルを更新したらある程度避ける問題ですが、メンテは面倒です。
  • SSL 証明書を発行出来ない
  • 簡単に外部からアクセス出来ない
  • 同じ LAN にあるデバイスでも簡単にアクセス出来ない

そういう時リバース SSH トンネルと言う裏技を使えばクリアできる!

ゴール

  • ローカルマシンで走っているサーバーを任意のドメイン名でアクセスできるように
  • SSL 接続が可能
  • SSL 証明書はLet’s Encryptで自動的に更新される
  • モバイル端末の設定(証明書、host files など)を変えず、自然にアクセスできる

リバース SSH トンネルとは?

基本的に、SSH トンネルの中に、逆方向の SSH トンネルを開くと言う事です。

クライアント(=ローカルマシン)がサーバーに接続して、更にサーバーのポートをクライアントに繋げるトンネルを作成する。

クライアントとサーバーの間にファイアウォールがあったとしても、サーバーと通してクライアントにアクセスできる。

オンラインサービス・ツール

待て待て待て、そういうのはすでに存在しているでしょう?

はい、おそらく一番人気のあるサービスは ngrok です。

ngrok は間違いなくとても便利で強力なツールです。

ngrok なら、カスタムドメインのため、有料プランが必要です。

ただし、最近の値上げの影響で(月$8から$20に)、諦めました。



じゃあ、他になにかあるのではないですか?実はたくさんあります。無料や低コストのオプションはいくつかがありますが、だいたい、自由にドメイン名を選べないまたは不安定であまり使えないサービスが多いです。

ソリューションにたどり着いた

SSHトンネルサーバー

これだ!

 

自分のサーバーにこのコードをデプロイすれば、

  • 自分の好みのドメインサブドメインを使用できる。
  • 自動的にLet'sEncryptでSSL証明証を発行してもらえる
    • ワイルドカード証明書で運用したいため、DNS バリデーションが必要
    • Route53プロバイダがあるから、インフラと簡単に連携できる
  • 特別なクライアントをインストールする必要がない
    • SSHのクライアントがあれば十分
  • 欲しければ、SSHの公開鍵でアクセス制限できる
  • 好きなサーバーでデプロイできるので、レイテンシに気にする必要がない

このトンネルサーバーはSSHサーバーですが、トンネル専用になっている、自分のポートでListenしている。

その他の材料

  • Docker
    • コンテナで走らせるのは一番楽
  • AWS
    • EC2でサーバーをホストする(もちろん Fargate などでも可能ですが、Fargate が使えるなら、自分でも改造できると思うから、この記事の対象外とさせていただく)。とりあえずサーバーで走るのが一番簡単
    • Route53(DNS)でドメイン名の確認を自動的に行うため

Dockerコンテナの準備

まずはEC2インスタンスにログインする。

ユーザーのホームで上記プロジェクトのコードをクローンする

git clone https://github.com/antoniomika/sish.git

これで sish フォルダができる。

~]$ ls -l
total 0
drwxrwxr-x 11 ec2-user ec2-user 317 Jun 16 07:20 sish

そのフォルダの中に入って、一旦新しいブランチで変更を行いましょう(野蛮人ではないから)

sish]$ git switch -c localconfig

そして、変えたいファイルは2つがある:

  • deploy/docker-compose.yml
  • deploy/le-config.yml

deploy/docker-compose.yml

デフォルト状態はこんな感じ

version: '3.7'

services:
  letsencrypt:
    image: adferrand/dnsrobocert:latest
    container_name: letsencrypt-dns
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./letsencrypt:/etc/letsencrypt
      - ./le-config.yml:/etc/dnsrobocert/config.yml
    restart: always
  sish:
    image: antoniomika/sish:latest
    container_name: sish
    depends_on: 
      - letsencrypt
    volumes:
      - ./letsencrypt:/etc/letsencrypt
      - ./pubkeys:/pubkeys
      - ./keys:/keys
      - ./ssl:/ssl
    command: |
      --ssh-address=:22
      --http-address=:80
      --https-address=:443
      --https=true
      --https-certificate-directory=/ssl
      --authentication-keys-directory=/pubkeys
      --private-keys-directory=/keys
      --bind-random-ports=false
      --bind-random-subdomains=false
      --domain=ssi.sh
    network_mode: host
    restart: always

自分の好みに変更しないといけない!

  1. 特定ポート番号( 2222 )で運用する。そうすれば、SSHとかぶらない
  2. ドメイン名を変える。仮に私の会社が totemohoge.comドメイン名を使ったら、サブドメインを自由に取れば困るだろう。よって、サブサブドメインでやる。このサーバーはtunnel.totemohoge.com になる。ユーザーが作成するトンネルはそのサブドメインの直下になる。例えば nasu.tunnel.totemohoge.com , kyuuri.tunnel.totemohoge.com などなど
  3. デフォルトタイムアウトは5秒でアップロードの時ちょっと厳しいと思うので、60秒まで上げる

パラメターはすべてcommandあたりで調整できる。詳細が必要だったら、プロジェクトのCLI Flagsセクションに参考してください。

するとdocker-compose.ymlはこうなる。

version: '3.7'

services:
  letsencrypt:
    image: adferrand/dnsrobocert:latest
    container_name: letsencrypt-dns
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./letsencrypt:/etc/letsencrypt
      - ./le-config.yml:/etc/dnsrobocert/config.yml
    restart: always
  sish:
    image: antoniomika/sish:latest
    container_name: sish
    depends_on: 
      - letsencrypt
    volumes:
      - ./letsencrypt:/etc/letsencrypt
      - ./pubkeys:/pubkeys
      - ./keys:/keys
      - ./ssl:/ssl
    command: |
      --ssh-address=:2222
      --http-address=:80
      --https-address=:443
      --https=true
      --https-certificate-directory=/ssl
      --authentication-keys-directory=/pubkeys
      --private-keys-directory=/keys
      --bind-random-ports=false
      --bind-random-subdomains=false
      --domain=tunnel.totemohoge.com
      --idle-connection-timeout 60s
      --authentication=true
    network_mode: host
    restart: always

deploy/le-config.yml

このファイルを使って、自動的にLet'sEncryptで証明書を発行してもらうようになる。

Let'sEncrypt で自動化するのはかなり便利で、とてもメンテしやすい。

ただし、ワイルドカード証明書を発行するため、DNS バリデーションをしないといけない。通常のHTTPバリデーションはドメイン毎に証明書を一つ発行する必要があって、かなり面倒で大変。

ワイルドカードだったら、一つで結構です。設定はちょっと大変ですけれど。

さて、やっちゃいましょう

デフォルトは以下の通りです。

acme:
  email_account: AUTH_EMAIL
certificates:
- autorestart:
  - containers:
    - sish
  domains:
  - ssi.sh
  - '*.ssi.sh'
  name: ssi.sh
  profile: cloudflare
profiles:
- name: cloudflare
  provider: cloudflare
  provider_options:
    auth_token: AUTH_TOKEN
    auth_username: AUTH_EMAIL

まずはメールアカウントを書かないといけない。LetsEncrypt から知らせが来るので、ちゃんと存在しているアドレスを使ってください(インフラ部のメーリングリストなど)。

次は証明書のドメイン一覧です。今回はトンネルサーバーのドメイン名とそのワイルドカードサブドメインが欲しいので、

  • tunnel.totemohoge.com
  • *.tunnel.totemohoge.com

になる。

name 属性は管理用の名前なので、分かりやすくするため、メインドメイン名にする

最後にプロファイル(=プロバイダー)の設定です。あれはプロバイダー毎設定は微妙に変わるので、Route53意外だったら、正しいパラメターを調べてください

Route53の場合は、プロバイダー名は route53 で、

  • auth_access_key
  • auth_access_secret
  • private_zone falseで固定(公開するから)
  • zone_id プロバイダーが変更できるDNSゾーンのID(セキュリティのため、専用ゾーンで運用する)

AWS の IAM で専用ユーザーを作って auth_access_keyauth_access_secret を発行して、取得できる。

AWSの設定

Route53 でホストゾーン作成して、一覧画面に戻れば、こんな感じです。

上記の zone_idは一番右のカラムの値です。ホストゾーンの詳細画面からでも取れる。

まずはEC2インスタンスへのDNSレコードを登録しないといけない。

tunnel.totemohoge.com ゾーンのなかでゾーンのトップレベルのAレコードを作り、EC2インスタンスのエラスティックIPを登録する。

そして、メインゾーンに参考レコードを作らないといけない。

tunnel.totemohoge.comのNSレコードを作って、ゾーン詳細画面からネームスペースサーバーをコピペする。

そして、IAM ユーザーを作成しましょう。APIのみのユーザー(Webコンソールアクセス不要)で、ポリシーはたった一つでいいです。 <ZONE_ID> だけを書き直してください。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "route53:ListHostedZones",
                "route53:GetChange",
                "route53:GetHostedZone",
                "route53:ListResourceRecordSets"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "route53:ChangeResourceRecordSets"
            ],
            "Resource": [
                "arn:aws:route53:::hostedzone/<ZONE_ID>"
            ]
        }
    ]
}

このポリシーのおかげでユーザーがこの特定ゾーンのレコードを自由に作成、編集、削除できる。

作成したユーザーのキーをメモって、 deploy/le-deploy.yml ファイルに貼ってください。

acme:
  email_account: naoto.date@totemohoge.com
certificates:
- autorestart:
  - containers:
    - sish
  domains:
  - tunnel.totemohoge.com
  - '*.tunnel.totemohoge.com'
  name: tunnel.totemohoge.com
  profile: route53
profiles:
- name: route53
  provider: route53
  provider_options:
    auth_token: <AUTH_ACCESS_KEY>
    auth_username: <AUTH_ACCESS_SECRET>
    private_zone: false
    zone_id: Z096534621PU0FUIVLDB2

そして、リンクをつくらなければならない:

sish]$ sudo ln -s /etc/letsencrypt/live/<ドメイン名>/fullchain.pem deploy/ssl/<ドメイン名>.crt
sish]$ sudo ln -s /etc/letsencrypt/live/<ドメイン名>/privkey.pem deploy/ssl/<ドメイン名>.key

ドメイン名は証明書の第一ドメイン名となるので、上記の設定だったら、 tunnel.totemohoge.com になるはずです。

準備が出来た

では実行する時です!

docker compose で管理されているので、かなり使いやすいです。

初期化の際に様子を見ていた方が良さそうです

deploy]$ docker-compose up

一番重要はLet’sEncryptとの連携です。最初はちょっと時間がかかることがあるので、心配しないで、結果を待ってください。

エラーが起きたら、まぁ・・・頑張ってくださいとしか言えない!エラーメッセージは結構丁寧で、原因が楽に特定できる。

特に問題がなければ、おめでとうございます。

Ctrl+Cで終了して、もう一度バックグランドで実行してください。

deploy]$ docker-compose up -d

これでサーバーが自動的に走るし、 letsencrypt コンテナのおかげで自動的に証明証を更新する。 restartalways に設定されているので、インスタンスを停止し再起動すれば、コンテナも自動的に立ち上がる。

公開鍵登録

ユーザーのアクセスを制限するために公開鍵サーバーに登録する必要がある。

だれでも勝手にSSHトンネルを登録できると困る!

やり方は非常に簡単です。

クライアントマシンの公開鍵を ~/sish/deploy/pubkeysにコピーすれば完了です。

基本的に通常のSSHのauthorized_keysファイル形式です。

認証なしで運用

deploy/docker-compose.yml ファイルを更新する必要がある。

      --authentication=true
を
      --authentication=false

にするだけで、公開鍵チェックをスキップできる。

またはパスワード認証も対応です。個人的に好きではないが・・・

では使ってみよう!

自分のマシン簡単なWebサーバー(例:ポート8080)を起動してから、次のコマンドを実行する

~]$ ssh -p <トンネルサーバーポート> <サブドメイン名>:80:localhost:<ローカルサーバーポート> <トンネルサーバー名>

今回の設定によると下記のようなコマンドです

~]$ ssh -p 2222 datenaoto:80:localhost:8080 tunnel.totemohoge.com

SSHクライアントのバージョンによって、エラーが発生する場合がある。

sign_and_send_pubkey: no mutual signature supported

その時、そのドメイン用のSSH config (通常 ~/.ssh/config)に下記の行を追加してください

Host tunnel.totemohoge.com
PubkeyAcceptedKeyTypes=+ssh-rsa

成功したらこういうメッセージが表示される:

~]$ ssh -p 2222 datenaoto:80:localhost:8080 tunnel.totemohoge.com
Press Ctrl-C to close the session.

Starting SSH Forwarding service for http:8080. Forwarded connections can be accessed via the following methods:
HTTP: http://datenaoto.tunnel.totemohoge.com
HTTPS: https://datenaoto.tunnel.totemohoge.com

ここまでたどり着いたら、どんなブラウザーでも開ける。

Ctrl+Cでトンネルを閉じれば、外部からアクセスできなくなる。

かなり便利なツールです!

最後に

sish はもっと色々できるアプリケーションです。HTTP・HTTPSのトンネルだけではなく、どんな TCP ポートも対応できる。一つのドメインを管理するだけではなく、どんなドメインでも使えるようにもできる。本当に素晴らしいツールです。詳しくは Github ページに参考してください。

今のところは数ヶ月続けて使っていて、特に問題がなかったです。

ただ、そのトンネルの存在を忘れないでください。トンネルサーバーもタイムアウトがあるので、自分に合わせたユースケースタイムアウトを設定してください。

sishプロジェクトが役に立てたら、開発者にビール一杯奢っていいと思います。🍺

 

株式会社フォトラクションでは一緒に働く仲間を募集しています

Pythonコードをドキュメント化した

はじめに

こんにちは、エンジニアの酒井です。

最近、チームで頻繁に使う関数、処理を共通ライブラリとして集約し、そのドキュメントを作成する業務に取り組みました。

調べたところPython製のドキュメントツールであるSphinxを使うと、ソースコードに記載したdocstringから自動でドキュメントを作成できるとのことなので、実際に使用してみました。

手順

ディレクトリ構成は以下のようになっており、sampleディレクトリ内にモジュールを一つ追加しています。

.
├── Dockerfile
├── docker-compose.yml
└── sample
    ├── __init__.py
    └── sample_1.py

1. Dockerfile, docker-compose.ymlの作成

FROM python:3.8

ENV APP_PATH=/code \\
    PYTHONPATH=.

WORKDIR $APP_PATH

RUN apt-get update && \\
    apt-get upgrade -y && \\
    pip install poetry

# `poetry init`をしたいので最初はコメントアウトする
# COPY poetry.lock pyproject.toml ./
# RUN poetry install

COPY . .

EXPOSE 8080
version: '3.8'

services:
  sample:
    container_name: sample
    build:
      context: .
      dockerfile: ./Dockerfile
    ports:
      - "8080:8080"
    tty: true
    volumes:
      - .:/code/
      - ${PIP_CACHE_DIR:-cache-sample}:/root/.cache

volumes:
  cache-sample:

2. poetryの導入、必要ライブラリのインストール

2-1. コンテナ上でbashを起動

$ docker-compose up -d --build
$ docker-compose exec sample bash

2-2. poetryの導入

# poetry init

2-3. 必要ライブラリをインストール

# poetry add sphinx sphinx_rtd_theme
  • sphinxsphinx_rtd_themeをインストールします。
  • pyproject.tomlに上記のライブラリが追記され、poetry.lock が生成されます。

3. ドキュメント作成

3-1. sphinx-apidocコマンドでドキュメント作成に必要なファイルを作成

# poetry run sphinx-apidoc -F -H sample -o docs sample
  • 指定したディレクトリは以下(今回はdocs)にファイルが生成されます。

3-2. 作成された conf.pyの編集

  • conf.pyでは大きく分けて、以下4つの設定が可能。

    1. Path setup(パスの設定)
    2. Project information(プロジェクト情報の設定)
    3. General configuration(一般的な設定)
    4. Options for HTML output(HTML出力に関するオプション)
  • 今回は以下の3つを変更する

    1. pathの指定
    2. 拡張モジュールの追加
      1. sphinx.ext.autodoc
      2. sphinx.ext.viewcode
      3. sphinx.ext.todo
      4. sphinx.ext.napoleon
      5. sphinx_rtd_theme
    3. デザインの設定(sphinx_rtd_theme
    # Configuration file for the Sphinx documentation builder.
    #
    # This file only contains a selection of the most common options. For a full
    # list see the documentation:
    # <https://www.sphinx-doc.org/en/master/usage/configuration.html>
    
    # -- Path setup --------------------------------------------------------------
    
    # If extensions (or modules to document with autodoc) are in another directory,
    # add these directories to sys.path here. If the directory is relative to the
    # documentation root, use os.path.abspath to make it absolute, like shown here.
    #
    
    import sphinx_rtd_theme # sphinx_rtd_themeのインポート
    
    import os # コメントアウトを外す
    import sys # コメントアウトを外す
    
    sys.path.insert(0, os.path.abspath('../')) # パスの指定(conf.pyから見てルートディレクトリを指定する)
    
    # -- Project information -----------------------------------------------------
    
    project = 'sample'
    copyright = '2022, sample'
    author = 'sample'
    
    # -- General configuration ---------------------------------------------------
    
    # Add any Sphinx extension module names here, as strings. They can be
    # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
    # ones.
    extensions = [
        'sphinx.ext.autodoc', # 追加
        'sphinx.ext.viewcode', # 追加
        'sphinx.ext.todo', # 追加
        'sphinx.ext.napoleon', # 追加
        'sphinx_rtd_theme' # 追加
    ]
    
    # Add any paths that contain templates here, relative to this directory.
    templates_path = ['_templates']
    
    # List of patterns, relative to source directory, that match files and
    # directories to ignore when looking for source files.
    # This pattern also affects html_static_path and html_extra_path.
    exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
    
    # -- Options for HTML output -------------------------------------------------
    
    # The theme to use for HTML and HTML Help pages.  See the documentation for
    # a list of builtin themes.
    #
    html_theme = 'sphinx_rtd_theme' # 編集
    html_theme_path = ["_themes", sphinx_rtd_theme.get_html_theme_path()] # 追加
    
    # Add any paths that contain custom static files (such as style sheets) here,
    # relative to this directory. They are copied after the builtin static files,
    # so a file named "default.css" will overwrite the builtin "default.css".
    html_static_path = ['_static']
    

3-2. HTMLファイルをビルドする

$ poetry run sphinx-build docs docs/_build

4. 動作確認

docs/_buildディレクトリに作成されたindex.htmlを確認すると、以下のようなページが出来上がっているのことが確認できる。

  • トップページ
  • モジュールのドキュメント例

5. 公開

これらをGitHubPagesやS3に配置し公開することでSphinxを用いたドキュメントが閲覧することができます。

※ 実際にはGitHub Actionsを用いて docs/_build 配下のファイルをS3にアップロードしてそれをCloudFront経由で配信するという形を取りました。こうすることでソースコードを変更するたびにドキュメントが更新され、運用がより楽になります。

次回記事を書く機会がありましたらまとめたいと思います。

さいごに

ドキュメントの作成をSphinxに任せることで共通ライブラリの管理、運用の手間が省けると感じました!

今後も業務の中で人が行う必要がない部分は積極的に自動化して、自分を含めチームメンバーが本質的な部分に力を注げるよう行動していきたいなと思います!

 

株式会社フォトラクションでは一緒に働く仲間を募集しています

エレガントなIoUの計算方法

はじめに

こんにちは、AIエンジニアの志賀です。

最近はとっても暑い毎日が続いていますね。つい1ヶ月前と比べるとこんな暑さは信じられないですね。冬には「極寒より猛暑の方がマシ」と考えるのですが、やはりいざ夏になると「猛暑より極寒の方がマシ」と考えてしまいます。

最近、行動経済学に関連した本を読んだりするのですが、これもアンカリング効果とやらの所業なのでしょうか?

さて、今回はIoUの様々な計算方法の中でも、numpyのclip関数を使用したIoUの計算方法を紹介していきたいと思います。個人的には、最もスマートでエレガントだなあ。と思った計算方法だったので、この計算方法を紹介する次第です。

おさらい

IoUとは

IoUとは、Intersection over Unionの略で、2つの領域の重なり具合を示す指標となっております。

具体的な計算方法は、仮にAとBの領域があった場合に、AかつB / AまたはBとなります。

より詳細を知りたい方はこちらの記事を参照すると分かりやすいです。

 

参考図:

intersection = オレンジ部分

union = オレンジ部分 + 青部分



numpyのclip関数を使ったIoUの計算方法

numpyのclip関数とは

numpyのclip関数は、入力されたnumpy配列の各要素を指定された任意の値の範囲に収める加工を行う関数です。clip関数の第一引数にnumpy配列、第二, 第三引数にそれぞれ最小値、最大値を入力します。

以下、使用例になります。

x = np.arange(10)
print(x)
# [0 1 2 3 4 5 6 7 8 9]

print(np.clip(x, 3, 6))
# [3 3 3 3 4 5 6 6 6 6]

numpyのclip関数を使ってどのようにIoUを計算するか

clip関数は、IoUの計算の中でも、分子であるintersectionの計算に使用します。

具体的には、x, y座標のそれぞれにおいて、片方のbox1の最小値最大値を使って、もう片方のbox2に対してnp.clipを適用します。

このようにする事で、box1とbox2が重なる部分、つまりintersection領域のみが残るという具合です。

例えば、box1 = [0, 0, 10, 10], box2 = [5, 5, 13, 13]だった場合、

box1の最小値、最大値を使用したclip関数をbox2に適用してあげると次のようになります。

box1 = np.array([0, 0, 10, 10])
box2 = np.array([5, 5, 13, 13])
x_min, y_min, x_max, y_max = box1
# x軸方向にclip関数を適用
box2[0::2] = np.clip(box2[0::2], x_min, x_max)
print(box2)
# [ 5  5 10 13]

# y軸方向にclip関数を適用
box2[1::2] = np.clip(box2[0::2], y_min, y_max)
# intersectionの領域を獲得
print(box2)
# [ 5  5 10 10] (= intersectionの領域)

あとは intersectionの領域の面積(=intersection)の計算及びunionを計算してあげれば、IoUが求まります。

unionは、box1の面積 + box2の面積 - intersectionによって求まります。

実際のコード

以下実際のコードです。

より利便性を追求する為に、1つの領域(box)と他の複数の領域(other_boxes)のIoUsをまとめて計算します。

def calc_ious(box, other_boxes):
		eps=1e-5
    other_boxes = np.array(other_boxes).reshape(-1, 4)
		#intersectionsの計算
    other_boxes_cpy = np.copy(other_boxes)
    np.clip(other_boxes_cpy[:, 0::2], box[0], box[2], out=other_boxes_cpy[:, 0::2])
    np.clip(other_boxes_cpy[:, 1::2], box[1], box[3], out=other_boxes_cpy[:, 1::2])
    inters = (other_boxes_cpy[:, 2] - other_boxes_cpy[:, 0]) * (other_boxes_cpy[:, 3] - other_boxes_cpy[:, 1])
		#unionsの計算
		box_area = (box[2] - box[0]) * (box[3] - box[1])
    other_boxes_areas = (other_boxes[:, 2] - other_boxes[:, 0]) * (other_boxes[:, 3] - other_boxes[:, 1])
    unions = other_boxes_areas + box_area - inters
		#iousの計算
    ious = inters / (unions + eps)
    ious = ious.tolist()
    return ious

box = [5, 5, 12, 12]
other_boxes = [[5, 5, 13, 13], [10, 10, 30, 30]]
ious = calc_ious(box, other_boxes)
print(ious)
# [0.7656248803711124, 0.008988763842949127]

感想

個人的には、この計算方法を知った時はスパイファミリーのヘンダーソン先生並みに「エレガント!」と感動したのですが、如何だったでしょうか。中にはそんなの最初から知ってるよという方もいるかもしれませんが、温かい目で見て頂けると嬉しいです。笑

 

株式会社フォトラクションでは一緒に働く仲間を募集しています

WEBエンジニアからQAエンジニアへキャリアチェンジして良かったこと

こんにちは。

PhotoructionでQAエンジニアをしている内田です。

私はPhotoructionでWEBエンジニア→QAエンジニアとしてのキャリアをスタートさせました。

今回はWEBエンジニアを経てQAエンジニアになったことで、活かせた経験やよかったことをお話させてもらおうと思います。

 

QAエンジニアへキャリアチェンジしてよかったこと

  • 機能に詳しくなれる。サービスの全体像がわかる

前職でWEBエンジニアとして働いているときは、自分が開発した機能以外の機能を知る機会がありませんでした。当時は「そんなことしてる暇があったら早く機能実装してよ」的な圧がありました。もちろんPhotoructionのWEBチームはそんな圧は無いです!

私は全体像をとらえてから詳細部分を知っていくタイプなので、QAエンジニアとしてサービスの機能全てを把握できたのが大変うれしかったです。サービスに対しての愛着も湧きました。

 

  • コードを書きたいときに書ける

WEBエンジニアとして働いていたときは毎日プログラムを書いていました。今現在QAエンジニアとしてコードを書くことはほぼありません。E2Eテストの実装で書こうと思えば書けるし、書く量もある程度自分で調節できるのが素晴らしいなと思います。

 

  • 開発の全行程に関われる

要望が挙がってきてからUIが固まるまでのいわゆる上流工程からリリース完了までずっとプロジェクトに参加できます。UIがFIXする前の段階で、ユーザー目線になって考えたときの良い所や改善点を伝えることができます。

その会社の開発手法にもよるかもしれませんが、私がWEBエンジニアのときはガチガチのウォーターフォール型開発でしたために上流工程にはあまり参加できませんでした。

QAとなった今ではがっつり関わることができるので、良いプロダクトを自分で生み出せている感覚があり、リリース完了したときの達成感が大きいです。

 

  • 他チームとの会話ができる

前述でも書きましたが、WEBエンジニア時代は他部署との関わりがあまりありませんでした。QAエンジニアはサポートチーム、WEBエンジニア、モバイルエンジニア、PMと非常に関わる人が多いです。

コミュニケーション取りながら仕事するっていいですよね。一人じゃない感があります。

 

WEBエンジニアを経てQAエンジニアになってよかったこと

  • 開発用語がわかる

こちらは言わずもがなですね。ワーカー環境、ビルドなどなど、開発時のエンジニアから発せられる用語が理解できました。踏み込んだ質問もエンジニアにできたりするので、WEBエンジニア時代の経験が活きてると感じます。

 

  • 不具合が出そうな箇所が感覚でわかる

プログラムを書くエンジニアにはわかると思うのですが、自分が出した不具合って強烈に覚えてますよね(笑)トラウマレベルの不具合もあるのですが、過去自分が出した不具合を参考にして「ここも不具合出そうだな」というアンテナが立ちます。

そのアンテナをテストケース化して不具合を検出した経験もあります。地味に一番よかったと思えることです。

 

  • 開発エンジニアと深堀った会話ができる

不具合が出た時に、「なぜ発生したか」「どうやったら再発防止できるか」といった会話ができました。技術的な話が出た場合は深堀った質問もできました。サービス品質の向上につなげられたので良かったと思います。

以上が私のWEBエンジニアを経てQAエンジニアになって良かったことになります。

最後まで読んでいただき、ありがとうございました。

 

株式会社フォトラクションでは一緒に働く仲間を募集しています

 

フォトラクション(建設業界特化型のSaaS)のテスターって実際どうなの?

QAエンジニアの山本です。

「フォトラクションのテスターって実際どうなの?」という疑問をお持ちの方もいらっしゃるかと思うので、今日はそれについてQ&A形式で回答していこうと思います。

Q&A

Q:仕事内容の詳細について教えて下さい。

A:主に下記の仕事をして頂きます。

  • (案件の)総合テスト・機能テスト
  • (リリース前の)リグレッションテスト
  • テストで検出したバグに関するバグチケットの起票
  • 本番環境で検出したバグに関するバグチケットの起票
  • お客様からお問い合わせ頂いたバグの調査・バグチケットの起票
  • バグの改修確認


Q:残業はありますか?

A:基本的には、ありません。

※もし今日が期限のタスクが時間内に終わらなかった場合でも、他の人に引継ぎをして、定時退勤が出来ます

 

Q:体調不良等による急な欠勤は出来ますか?

A:はい。出来ます。

 

Q:フルリモートワークですか?

A:はい。フルリモートワークです。

 

Q:地方在住/地方へ移住したいです。

A:地方在住/地方へ移住したい方も積極採用中です。

 

Q:コミュニケーションはどうしていますか?
A:普段はSlackで業務連絡をし、会議はGoogle Meetで実施しています。

 

Q:強制参加のイベント等はありますか?

A:ありません。

イベント自体はたまに(年に数回程度)ありますが、任意参加です。

 

ーーー下記の質問はテスターさんに、直接ご回答頂きましたーーー

 

Q:社内の人間関係はどうですか?

A:皆さん親切丁寧で、分からないことも聞きやすい環境です。

おかげさまでストレスなく作業ができています。

(Iさん)

 

何か困ったことがあった時でも質問をすればすぐに対応してくれる等、優しい人が多いので働きやすい。

一方、業務がフルリモートなので深い人間関係の構築を図ろうとすると難しいということもある。

(Kさん)

 

わからないことは、すぐに聞ける環境であり、皆さん優しく教えてくださりますので、

人間関係は良好だと思いました。

(Yさん)

 

Q:応募する前と実際に働き始めてからのギャップはありますか?

A:テスターと聞くと、淡々とバグを見つける作業だと思っていましたが、いざやってみるとバグの原因調査、定期テスト、改修確認と様々な工程があり、やりがいのある仕事だと思いました。

(Iさん)

 

SlackというSNSツールで基本的に連絡を取り合うので、一緒に働いている人と顔を合わせる機会がないのでは?と考えていたのですが、週1でオンラインによる会議(作業の進捗確認等)があるので意外に顔を合わせる機会はあること。

(Kさん)

 

まとめ

ここまで、当社でテスターとして働く事の魅力についてお伝えしてきました。

もちろん当社はスタートアップなので課題は色々あります。

ただその課題を踏まえた上でも、私は当社で働く事に魅力を感じています。

それはネガティブな事でも率直に相談出来るメンバーがいるので、数年後必ずそれらの課題を解決出来ると信じているからです。

最後まで読んで頂き、ありがとうございました(^_^)!!

 

株式会社フォトラクションでは一緒に働く仲間を募集しています

スクラム開発始めました!!

はじめに

こんにちは!株式会社フォトラクションでWebエンジニアをしています南風原です。

早速ですが、タイトルにもある通り私たちテクノロジーサービス部は3月から本格的にスクラム開発を始めました!!👏👏👏

今回はスクラム導入にあたっての取り組みや導入後の経過などを私目線でまとめてみたのでぜひ最後まで読んでいただけると嬉しいです!

スクラム導入の背景

これまでウォーターフォールのようなフローで開発を行ってきたのですが様々な問題が発生するようになりました。

問題点の一部を挙げると以下のようなものがありました。

  • プロジェクトの規模が大きくなりリリースするまでの時間が長い
  • 改修による影響範囲が大きくなりがちであり工数が増える
  • プロジェクトごとにチームが結成されリリースしたら解散されるためチームとしてのノウハウの蓄積やコンセンサスを取りながらの開発がしにくい状態になっている
  • 上から降りてくる要件を作るといった状況が多く社内受託感が強い

このような問題点から開発体験がよろしくない状態にあり、開発チームとしてこの状況を改善していく必要があると考えました。

そこで、従来の開発体制を改善していくためにウォーターフォールのような開発からアジャイルな開発にシフトしていく取り組みを始めることになりました。

スクラム導入までの流れ

アジャイルな開発にしていこう!スクラム開発やるぞー!と言ったものの社内にそういったことに知見がある方が当時はいなかったため、アジャイルコーチとして永和システムマネジメントさんにサポートしてもらいながらスクラムへの移行を行なっていきました。

スクラム移行に向けて取り組んだこととしては以下になります。

  • 開発フローの現状把握と問題点の洗い出し
  • フォトラクションが望むアジャイルの進め方の提案と移行するにあたってのアクションの相談
  • プロダクトロードマップの作成とチーム体制の検討

スクラム開発スタート

3月から開発組織の構成をガラッと変更しスクラム開発を開始しました。

これまではWebグループ、モバイルグループといった技術領域でグループ分けをしていましたが、これからはプロダクトチーム軸でチームを作りそれぞれのチームに開発テーマがありチームドリブンで開発していくような組織に変更されました。

さらに、より良いスクラム開発を進めて行くために、各チームのスクラムマスターには認定スクラムマスター研修を受講してもらいました。(ありがたい👏)

スクラム導入後の現状

私が所属するチームでの話になりますが、実際にスクラムを導入してチーム開発を行なってきて2ヶ月くらい経ったので今の現状とこれからについて書いていこうと思います。

まず初めに、個人的な話からすると、私自身所属チームではスクラムマスターも担当させていただくことになりました。

ただ、スクラム初心者だったのでそもそもスクラムとはなんぞや?から勉強を始めました。

スクラムに関する勉強で取り組んだことは以下になります。

チームとしてもスクラム経験者が少なかったのでチーム内でもスクラムに関する勉強会を開催しました。

チームメンバー全員でスクラムに対しての疑問点やチームとしてどういうふうに進めて行くのがいいのかなど話し合う時間を作りました。

現状、スクラム開発を始めて2ヶ月ほど経過しましたがまだまだ手探りで進めているところもあり、ちゃんとスクラムとして運用できている状態かでいうとまだまだな部分もあります。

ただ、スクラム導入前よりもチームメンバー内でコミュニケーションを取って進めていく形が取れているので引き続きよりいい状態でチーム開発が行えるように取り組んでいきたいと思います。

今後の展望

私個人としては、スクラムマスターとしてまだまだ未熟な部分が多いので自分のチームがよりいい形でスクラム開発を行なっていけるように精進していきたいと思います。

方法としては、他チームのスクラムマスター同士での振り返りを通してチーム間の意見交換を行ったり、スクラムに関する情報のインプットを継続して行うのとそれをチームにも発信していけるようにしたいです。

開発組織全体でいうと、スクラムを導入してこれまでの開発体制を改善していきつつあるもののまだまだ改善するべき問題が多いです。その課題を開発組織一丸となって解決していき価値あるプロダクトをユーザーに提供し続けられる強い組織を作っていくためにも一人一人が自分にできること、チームに貢献できることは何かを常に考えて行動していくのが大事なのかなと思った次第です。

よりいい環境で、楽しく働き続けるためにも自分たちの手でどんどん改善していきたいものですね😉

以上、ざっくりした内容になりましたが、スクラム開発始めたよって話でした〜👏

最後まで読んでいただきありがとうございました!

株式会社フォトラクションでは一緒に働く仲間を募集しています

AWS Lambdaを変えてしまったかもしれないお話

PhotoructionのWebエンジニアの下川原です。

今回は、AWS Lambdaについて語ろうと思います!

 

関連ワード

  • Lambda
  • nodejs
  • sharp
  • 画像処理
  • 雑談

 

もくじ

 

1.AWS Lambdaが変わったこと

参考:https://aws.amazon.com/jp/blogs/aws/aws-lambda-now-supports-up-to-10-gb-ephemeral-storage/

Lambdaはもともと512MBのtmpが提供されていた。

しかし、2022/3/24にアップデートがされた。要約すると

Lambdaが提供するtmpを512MBから10GBまで増やせるよ!

※ただし512MB以上の容量追加は追加料金あるよ!

やったね!

2.Lambdaに何をしてしまったのか

'use strict';
const sharp = require('sharp');

exports.mainHandler = async (event, context, callback) => {
    let work_folder = createWorkDirectoryStructure();
    //画像をDLしてくる処理
    let src = getSourceData(event.data.bucket, event.data.image_path, work_folder);
    toProcessImage(src, work_folder, event.data.image_name);
    CleanupTempDirectory(work_folder);

    return true;
};

const toProcessImage = async (src, output_base_path, slice_file_base_name) => {
    let src_img = sharp(src);
    let tmp_path = path.join(output_base_path, slice_file_base_name);

    //切り取り処理 数字は適当
    await src_img.extract({
        left: 100,
        top: 200,
        width: 500,
        height: 500,
    })
        .toFile(tmp_path);//加工した画像を配置する処理
}

const createWorkDirectoryStructure = () => {
    let work_folder = path.join('/', 'tmp', 'work-' + shortid.generate());
    if (!fs.existsSync(work_folder)) {
        fs.mkdirSync(work_folder);
    }
    return work_folder;
}

const getSourceData = (bucket, key, directory) => {
    let file_extension = path.extname(key);
    let dst = path.join(directory, `src${file_extension}`);
    return downloadFile(bucket, key, dst);
}
//ディレクトリを削除する処理
const CleanupTempDirectory = (dir) => {
    if (fs.existsSync(dir)) {
        fs.rmdir(dir, {"recursive": true}, (error) => {
            if (error) {
                console.log("clean up progress: " + error);
            }
        });
        console.log("Cleanup complete");
    }
}

こんなように、適当に画像を切り出す処理があったとする。

AWSコンソールから、

テスト実行・・・成功

テスト実行・・・成功

テスト実行・・・失敗

エラー:No space left on device

短い期間、ほぼ連続で実行するとエラーになる

なぜ?

容量不足と出ているなら、この処理のどれかが悪さをしているのだろう。

・画像をDLしてくる処理

・加工した画像を配置する処理

ディレクトリを削除する処理

Lambdaコンテナの中で何がおきてるか、確認するのが早いだろう(Linuxのコマンドが使えてよかった・・)

処理のはじめと終わりに以下のコードを追加

console.log(execSync("df -h").toString());
console.log(execSync("du -h /tmp").toString());

コード挿入後の実行ログ

1回目テスト実行・・・成功

INFO	Filesystem                                                       Size  Used Avail Use% Mounted on
/mnt/root-rw/opt/amazon/asc/worker/tasks/rtfs/nodejs14.x-amzn-2  9.8G  8.8G  947M  91% /
/dev/vdb                                                         1.5G   14M  1.4G   1% /dev
/dev/vdd                                                         526M  872K  514M   1% /tmp
/dev/root                                                        9.8G  8.8G  947M  91% /etc/passwd
/dev/vdc                                                         9.5M  9.5M     0 100% /opt
INFO 4.0K	/tmp
INFO Cleanup complete
INFO Filesystem                                                       Size  Used Avail Use% Mounted on
/mnt/root-rw/opt/amazon/asc/worker/tasks/rtfs/nodejs14.x-amzn-2  9.8G  8.8G  947M  91% /
/dev/vdb                                                         1.5G   14M  1.4G   1% /dev
/dev/vdd                                                         526M  178M  337M  35% /tmp
/dev/root                                                        9.8G  8.8G  947M  91% /etc/passwd
/dev/vdc  
INFO 4.0K	/tmp
2.7M	/tmp

2回目テスト実行・・・成功

INFO	Filesystem                                                       Size  Used Avail Use% Mounted on
/mnt/root-rw/opt/amazon/asc/worker/tasks/rtfs/nodejs14.x-amzn-2  9.8G  8.8G  947M  91% /
/dev/vdb                                                         1.5G   14M  1.4G   1% /dev
/dev/vdd                                                         526M  178M  337M  35% /tmp
/dev/root                                                        9.8G  8.8G  947M  91% /etc/passwd
/dev/vdc                                                         9.5M  9.5M     0 100% /opt
INFO 4.0K	/tmp
INFO Cleanup complete
INFO Filesystem                                                       Size  Used Avail Use% Mounted on
/mnt/root-rw/opt/amazon/asc/worker/tasks/rtfs/nodejs14.x-amzn-2  9.8G  8.8G  947M  91% /
/dev/vdb                                                         1.5G   14M  1.4G   1% /dev
/dev/vdd                                                         526M  355M  160M  70% /tmp
/dev/root                                                        9.8G  8.8G  947M  91% /etc/passwd
/dev/vdc  
INFO 4.0K	/tmp
2.7M	/tmp

3回目テスト実行・・・失敗

INFO	Filesystem                                                       Size  Used Avail Use% Mounted on
/mnt/root-rw/opt/amazon/asc/worker/tasks/rtfs/nodejs14.x-amzn-2  9.8G  8.8G  947M  91% /
/dev/vdb                                                         1.5G   14M  1.4G   1% /dev
/dev/vdd                                                         526M  355M  160M  70% /tmp
/dev/root                                                        9.8G  8.8G  947M  91% /etc/passwd
/dev/vdc                                                         9.5M  9.5M     0 100% /opt
INFO 4.0K	/tmp
INFO Cleanup complete
INFO Filesystem                                                       Size  Used Avail Use% Mounted on
/mnt/root-rw/opt/amazon/asc/worker/tasks/rtfs/nodejs14.x-amzn-2  9.8G  8.8G  947M  91% /
/dev/vdb                                                         1.5G   14M  1.4G   1% /dev
/dev/vdd                                                         526M  512M  2.4M 100% /tmp
/dev/root                                                        9.8G  8.8G  947M  91% /etc/passwd
/dev/vdc  
INFO 4.0K	/tmp
2.7M	/tmp

順調に増えてる・・・

画像はそこまでインパクトは無い・・・

作業ディレクトリはエラー無くしっかり消せている・・・

・画像をDLしてくる処理

・加工した画像を配置する処理

ディレクトリを削除する処理

    //切り取り処理 数字は適当
    await src_img.extract({
        left: 100,
        top: 200,
        width: 500,
        height: 500,
    })
        .toFile(tmp_path);//加工した画像を配置する処理

どうやら、toFile時に

関数とは違う実行ユーザーでファイルが置かれてしまうらしい

lrwx------ 1 sbx_user1051 990 64 Dec  3 01:43 24 -> /tmp/vips-0-3816661850.v (deleted)
lrwx------ 1 sbx_user1051 990 64 Dec  3 01:43 25 -> /tmp/vips-1-2724960011.v (deleted)
lrwx------ 1 sbx_user1051 990 64 Dec  3 01:43 26 -> /tmp/vips-2-485543279.v (deleted)
lrwx------ 1 sbx_user1051 990 64 Dec  3 01:43 27 -> /tmp/vips-3-930892267.v (deleted)

どおりで、/tmp配下に存在しない”ように”見えてしまっていたわけ

3.解決方法

では、上記の問題をどう解決したか?

ファイルを開く時に、メモリ経由かディスク経由かを判定するために

サイズを指定するパラメータ(VIPS_DISC_THRESHOLD)があり、

これを適切に設定しないと、ストレージが枯渇してしまう問題を見つけた。

https://github.com/lovell/sharp/issues/707

https://github.com/lovell/sharp/issues/1851

つまり、VIPS_DISC_THRESHOLDの設定を極端に少なくすれば常にメモリ経由で処理を継続してくれるというのだ。

Lambdaの環境変数に次のように追加することで設定可能

VIPS_DISC_THRESHOLD=10M

すると何回連続して実行を行っても、問題がおきることは無くなった。

4.さいごに

このような問題を発見、解決した数日後に「tmpを拡張可能にしたよ!」の記事が・・・。

さすがに数日で対応したわけは無いと思う、自由に使えるtmp容量を増やしてほしいという声は、

LambdaでAWS EFSを活用する記事をみると前々から需要があったのだろう。

気軽にtmpを拡張することができるので、ディスク上で操作する幅が広がったLambda

最後に改めて紹介しよう。

https://aws.amazon.com/jp/blogs/aws/aws-lambda-now-supports-up-to-10-gb-ephemeral-storage/

 

株式会社フォトラクションでは一緒に働く仲間を募集しています