Pythonで文章翻訳を試してみる

こんにちは、いつものtaanatsuです。
今回は、翻訳というものをやってみたいと思います。

それではやっていきましょうか!

やってみたいこと

文章を翻訳したい!

見つけたもの

EasyNMTというライブラリ。
これを使ってみます。

環境準備

今回もvenvを使っていきます

python -m venv venv
source venv/bin/activate

必要モジュールのインストール

pip install -U easynmt

protobufのバージョン指定があるので、合わせてあげる

pip install -U protobuf~=3.20.0

Pythonのコード

「main.py」として保存したとします。

from easynmt import EasyNMT
model = EasyNMT('mbart50_m2m')

sentence = 'はじめましてこんにちは。今日はいい天気ですね!'

print(model.translate(sentence, target_lang='en', max_new_tokens=1000))

え!?これだけ!?
これだけです。
すごいですよね。

実行

python main.py  

初回はモデルデータのダウンロード(2〜3GB)が入ります。
その後プロンプトに以下の文字が出ると思います。

Hello, it's good weather today!

いい感じですね!

英語を日本語にしてみる

from easynmt import EasyNMT
model = EasyNMT('mbart50_m2m')

sentence = 'Hello, it\'s good weather today!'

print(model.translate(sentence, target_lang='ja', max_new_tokens=1000))

こんにちは、今日は天気がいいです!

翻訳されました!

まとめ

今回は簡易的な翻訳ツールを試してみました。
モデルのロードなど結構遅いのでそのあたりの高速化も考えてみたいですね。

それでは、また次回!

Pythonで文章要約!自然言語処理を使ってExciteの記事を要約してみた

こんにちは。
いつものtaanatsuです。

今回は、自然言語処理で文章要約をしてみます。
それではやっていきましょうか。

ターゲット

エキサイトニュースの記事
カーシェアリング各社を比較 タイムズ、カレコ、オリックスの対抗にdカーシェア
を要約してみます! 。 (正しく要約できているかは、記事に飛んでチェックしてみてください!)

バーチャルenv環境の準備

Python標準の venv を使っていこうと思います。

# バーチャルenvの作成
$ python3 -m venv venv

# ターミナルにバーチャルenvを反映
$ source venv/bin/activate 

必要モジュールのインストール

$ pip install sumy
$ pip install tinysegmenter
$ pip install ginza ja-ginza

実行コード

「LexRank」というアルゴリズムを使ってみます。
このアルゴリズムGoogle検索のPageRankを応用したアルゴリズム「TextRank」に加え、
TF-IDFという特徴量を加味しています。

from lxml.html._diffcommand import read_file
from sumy.parsers.plaintext import PlaintextParser
from sumy.nlp.tokenizers import Tokenizer
from sumy.summarizers.lex_rank import LexRankSummarizer

# 要約対象のテキスト
text = read_file('./sample_text.txt')

# 3行要約をする(sentences_count=3なので、3行)
parser = PlaintextParser.from_string(text, Tokenizer('japanese'))
summarizer = LexRankSummarizer()
res = summarizer(document=parser.document, sentences_count=3)

for sentence in res:
    print(sentence)

要約結果「3行で頼む!」

カーシェアリング各社を比較 タイムズ、カレコ、オリックスの対抗にdカーシェア」の3行要約!
※ 元文章は一部画像削除や不要な文削除(箇条書きやカーシェアを使ってみた感想部分の削除)など、1〜2分でできるチューニングは行っています

カーシェアリングは、自家用車を購入するほど頻ぱんにクルマに乗らないが、買い物などちょっと出かけるときに使いたいという人向けの選択肢として、年々注目度が上がっている。
用途でいえば、各社が運営している駐車場の一画が車両ステーションになっているため拠点数が多く、短時間からの利用ができるので、週末のおでかけやちょい乗りに適している。
10~15分単位で料金が設定されており、1時間の利用で800円前後(月額基本料、距離料金を除く)だ。

カーシェアのまとめのようなものが出力されました。
各サービスのまとめではなく、カーシェアリングのまとめなのでタイトルとは少し乖離がありますね。
元文章をもう少しチューニングすれば、もっと精度良く出せるようになるのでしょうか?
また色々試してみたいですね!

結果

サクッと作った割には、要約的な意味では良さそうな結果となりました。
タイトルに近づけるには、各サービスの比較の文章も出てきてほしいなーってところですね。
チューニングを挟めばもっと精度も上がりそうな予感がしますね。

自然言語処理に興味があるエンジニアを、エキサイトでは募集しています!
よかったら一緒に働いてみませんか?

www.wantedly.com

と、いうわけで、今回は自然言語処理で遊んでみました。
では、また次回ー!

MacにAzure CLI をインストール

こんにちは、エキサイト新卒1年目、趣味は自宅鯖(オンプレ)のNOGU(@NOGU_D626🐤)です。
入社してかれこれ5ヶ月が経とうとし、自分の所属しているメディア事業部では怒涛のブログシステムのインフラ部分を現在更新中(訳あって今までやってなかった多分)。
そこで今回得られた知見などを元に後世の方々への知見として色々書いていこうと思います。

前置き長くなりましたがそのNOGUの投稿の第一弾となります👏
エキサイトのブログは現在azureで動いてるためそれに関連し今回、Azure CLImacへのインストールについて書いていこうと思います。

前提

Azire CLI は、Mac 以外にも、Linux、Windowns も対応しておりますが、今回自分自身の環境がMacということもありMacに限った話で進めていきます。
またazure上に自身のアカウントを作成しているものとして進めます。(ログイン確認の部分で必要) 
画像やコンソールの結果等などに関しては、個人アカウントでの検証を行っておりプライバシの問題やセキュリティの観点から一部マスクした状態で掲載させていただきます。 あらかじめご了承ください。

インストール作業

Mac では Homebrew を使ってインストールことができるためHomebrew経由でのインストールを行います。
まずローカルのHomebrew リポジトリをアップデートしたのちにインストールを行います。

$ brew update

$ brew search azure-cli
==> Formulae
azure-cli 

$ brew install azure-cli

インストールが完了するとazコマンドをterminal上で利用することができるので、実際にインストールされたバージョンを確認したいと思います。

$ az --version
azure-cli             2.39.0

core               2.39.0
telemetry             1.0.6 *

Dependencies:
msal              1.18.0b1
azure-mgmt-resource       21.1.0b1

CLIからAzure にログイン

az loginコマンドを使用しAzure にログインすると、サブスクリプション情報などのAzure アカウントに紐づいた情報を取得または利用することが可能になります。
実際にterminalに以下のコマンドを入力しログインをしてみましょう。

$ az login                                         

コマンドを入力する自動的にブラウザが立ち上がり以下のような画面が表示されると思います。
対象のアカウントにてログインを行うことでAzure アカウントに紐づいた情報CLIから利用することが可能となります。

azure portal ログイン

アカウント認証が成功すると以下のような結果がターミナル上に表示されます。

[
  {
    "cloudName": "AzureCloud",
    "homeTenantId": "xxxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx",
    "id": "xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx",
    "isDefault": true,
    "managedByTenants": [],
    "name": "Azure subscription 1",
    "state": "Enabled",
    "tenantId": "xxxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx",
    "user": {
      "name": "nogu@△△△△.com",
      "type": "user"
    }
  }
]

ブラウザ以外でもコマンドラインからユーザ名とパスワードを指定してログインすることも可能です。
実際の手順に関しては省略させていただきコマンドのみ以下に掲載します。

# ユーザー名をパラメータで指定
az login -u {登録した際のメールアドレス}

# ユーザー名とパスワードをパラメータで指定
$ az login -u {登録した際のメールアドレス} -p {登録した際のパスワード}

az コマンド ヘルプ機能

コマンドの意味や現在使用できるコマンドなどについて確認することができます。
以下のコマンドを実行して実際に確認してみましょう。

$ az --help

インターンの業務で初めてのバナー制作体験!

自己紹介

初めまして!今回exciteさんの就業型のインターンに参加させていただいた、千葉工業大学大学院修士1年の増村和也と言います! 大学では主にUXやサービスデザインなどを主に勉強しています。 趣味は女性アイドルと昔のアニメが好きです!

今回は初めてバナーの制作に挑戦しました!

手順

エミニナルのバナーを制作するにあたり

1.エミニナルのサイトの分析

2.類似バナーのリサーチ

3.制作

4.アドバイスを頂きBUを繰り返した

1.エミニナルのサイトの分析

私はまずはエミニナルの公式サイトの全体の雰囲気や、伝えたい内容、色彩を分析しました。 全体的に温かい印象で、透明で目立ずに自宅に届くことが特徴であるマウスピース矯正ということを分析することが出来ました。

2.類似バナーのリサーチ

次に、エミニナルと雰囲気が類似しているバナーをリサーチしました。そこで、集めたバナーを分類分けしてエミニナルの雰囲気にあった「温かさ」のバナーを参考にして制作に取り組みました。

3.制作

1と2でリサーチと分析した内容をもとに数種類のバナーを制作しました。1つのバナーに対して数種類のパターンと色合いのバナーをを制作し、とにかく種類を出しました。

4.アドバイスを頂きBUを繰り返した

制作したバナーに対してメンターの方にアドバイスを頂き、1週間かけてBUを繰り返しました。 BUのアドバイスとしては、

1.文字のジャンプ率を意識する

2.文字の色と背景の色をグレートーンにして確認する

3.figmaプラグイン機能を効率的に活用する。

4.スマホの画面に合わせて全体や文字のサイズを調整する

という5つのポイントを改めて学ぶことが出来ました。

割と同じことの繰り返しでしたが、全体的に楽しく経験することが出来ました。 実装されるのが楽しみです!

【インターン】デザイン初学者がはじめてバナーを作って学んだこと

こんにちは!
エキサイトで就業型インターンをさせて頂いている齋藤と申します。
今回は、インターンの中で初めての業務である「バナー制作」を通じて学んだことを共有したいと思います。

自己紹介

名古屋在住の大学3年生です。大学では政治学(主に政治過程論や行政学)を専攻し、ゼミでは文部科学省の研究をしたりしています。ちなみに、上の画像は趣味で購入した衆議院議員バッジもどきです。

デザイナー志望なのに美大でもデザイン専攻でもないため、今までデザインを体系的に学ぶ機会はなく、完全に独学でデザイン"的"なものを今までやってきました。
デザイナーの卵になるべく、デザインの経験や技法を身につけたいと考えていたところ、エキサイトの就業型インターン「Booost!!!」に出会い、ジョインすることに決めました。

はじめての業務でやったこと

インターンではじめて頂いたお仕事は、エキサイトの歯列矯正サービスの「エミニナル」のインスタグラム投稿用のバナー制作でした。
はじめてのMac、はじめてのFigmaとはじめてだらけで右も左もわからない状態でしたが、とりあえずやってみよう精神で制作に着手しました。

はじめての業務で学んだこと

メンターさんを始め、現場で活躍されているデザイナーの方々にご指導いただくことを通じて多くのことを学びました。

デザイン制作の前準備の重要さ

1つ目のデザイン案制作の際は、下準備など特段気にもせずに過去作品やLPからなんとなく雰囲気を感じ取り、見様見真似で作業を行なってしまいました。結果として、「不作法」で「裏付けのない」デザインとなってしまいました...
この失敗から、ターゲット設定雰囲気設定、掲載情報の優先順位設定などの前準備をすることの重要さを思い知りました。

デザイン力はリサーチ力

「敵を知り、己を知れば百戦危うからず」という言葉の通り、先述の前準備の段階で関連する自社のデザインはもちろん、競合他社のデザインもリサーチをすべきであることに気が付きました。
そこで、 ターゲット設定等とリサーチを行う「リサーチボード」を作成し、資料をまとめるようにしました。

前準備、リサーチを行い、2つ目のデザイン案を制作した結果、少し理にかなったものが完成しました。

オフラインでのリサーチがかなり有用

先述のリサーチに関して、オンラインでのリサーチをした一方でもっと観察したいという欲望に駆られ、持ち前の行動力(?)で近所のドラッグストアのコスメコーナーに足を運び、ブランドイメージが合う商品のディスプレイを偵察しました。
これに味を占め、デパートにも足を運びプチプラからデパコスまで多種多様なディスプレイを観察し、イメージとアイデアを膨らませました。(今振り返るとかなり怪しい人なので、大きな声でおすすめはできません...)

リサーチとアイデアの試行錯誤を繰り返した結果、最終的に3つ目のデザイン案が仕上がりました。

他にも気づきや学びが沢山ありますが、盛りだくさんになってしまうのでまた機会があれば共有していきたいと思います...!(連載を勝手に期待)

さいごに

まだインターンが始まって1週間しか経っていませんが、すでに多くのことを学び得ることができています。正直、毎日が楽しくて楽しくてたまりません。貴重な経験、知識、技術を与えてくださるエキサイトの社員さんに感謝しかありません。
デザイナーになるべく成長するために、知識技術すべてを盗んで自分のものにするぐらいの気持ちで今後も業務に粉骨砕身で取り組んでいきます!!

build.gradleのbuildscript、plugins、dependenciesの違いについて

こんにちは。 エキサイト株式会社の三浦です。

Javaの依存管理にGradleを使用する場合、build.gradleファイルを使うことになります。

ですがbuild.graldeでライブラリを管理する場合、 buildscriptpluginsdependencies といった複数の書き方があります。

今回は、それぞれの違いについて説明していきます。

buildscript plugins dependencies の違い

buildscript plugins dependencies の3つとも、「ライブラリをインストールする」という意味では同じです。

ですが、細かく見ると以下のような違いがあります。

dependencies の使い所

dependencies は、アプリケーションコード内で使いたいライブラリをインストールするために使用します。

例えば、アプリケーションコード内でAPIにアクセスしたい場合は、 dependencies を使用してHTTPリクエスト用のライブラリをインストールする、といった形です。

allprojects {
    repositories {
        mavenCentral()
    }
}

subprojects {
    // 必要になる部分で定義
    dependencies {
        implementation "org.apache.httpcomponents:httpclient:4.5.13"
    }
}

buildscript plugins の使い所

buildscriptplugins は、ともにbuild.gradle内で使いたいライブラリをインストールするために使用します。

例えば、コードフォーマットをgradleのタスクとして定義したい、という場合は、build.gradle内でコードフォーマット用のライブラリが必要になるので、 buildscriptplugins でコードフォーマット用ライブラリをインストールする、といった形です。

なお、buildscriptplugins の使い分けですが、基本的には plugins で使用できるようにライブラリが開発されていたら plugins を使用するのが良いようです。

plugins の方が新しく出来たインストール方法であり、 buildscript に比べて簡単に記述できるようになっています。

pluginsの使用例

// 可能な限りファイルの最初の方で定義
plugins {
    id 'checkstyle'
}

buildscriptの使用例

// 可能な限りファイルの最初の方で定義
buildscript {
    repositories {
        mavenCentral()
    }

    // buildscript内で使用するdependenciesは、あくまでbuildscript内で効力を持つものであり、
    // 通常のdependenciesの様にアプリケーションコード内で使用できるようにはならない
    dependencies {
        classpath platform('software.amazon.awssdk:bom:2.17.160')
        classpath 'software.amazon.awssdk:s3'
    }
}

最後に

build.gradleは、できることがたくさんある分かなり複雑な設定ファイルです。

この記事が、その理解のための一助となれば幸いです。

aws cloudwatch insight で apache access_log から簡素なアクセス解析を行う

システム開発部の @nukisashineko (ぬさし) です。

記事の対象者

aws cloudwatch logs insight に興味がある人。
aws ドキュメントの色々な要素を複合した query の作例を見てみたい人。

  • 正規表現 parse
  • fields を追加しつつ、特定のフィールドの内容をカスタマイズ
  • stats を複数指定して SQL で言うところの group by な関数として振る舞わせる

やりたいこと

aws cloudwatch log に apache access_log を流しています。
すごくざっくりとアクセス解析をしたくなったので、
insight の query を書いて簡素なアクセス解析を行いました。

やりたいことは下記です。

  • 簡素なアクセス数のカウント
    • 数字列は * へ変換
    • URL の Get parameter を無視
    • Bot や 死活監視は対象外とする

apache の log format

LogFormat "%v %{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %D" combinedvh

cloudwatch log insights query

# 注意: 検索時の指定時間を無制限にするとお金がかかるので、1日以下で区切って利用すること
# 説明:
#    - "指定時間帯における URI Path (Get query parameters を含まない) ごとの成功済みアクセス数を 数字部分unique して取得するためのクエリ"
#        - 利用されている api や path の判定で、棚卸しの際に利用する
#            - 複数のサービスが一台に乗っている場合の filter を "コメントアウト設定" で追加
#            - aws の health check へのアクセスを排除
#            - bot 系の UA を排除
#            - 重複 slash 部分を "/" のようにまとめるために置換処理を行っている
#            - 数字部分を "/xxxxxx/*/yyyy/" のように アスタリスクにまとめるために置換処理を行っている
#            - ※ トレイリングスラッシュ有無の重複削除の考慮は現状未実装
#        - アクセス数 (uri_path_count), 平均レスポンス秒数(avg_responseTime_seconds), 合計レスポンス秒数(sum_responseTime_seconds) を一緒に吐き出す

fields @timestamp, @message
| parse @message /\s*(?<srcIpAddress>.*?)\s+(?<srcUser>.*?)\s+(?<remoteUser>.*?)\s+\[(?<timestamp>.*?)\]\s+"(?<httpMethod>.*?)\s+(?<requestUriPath>[^\?]*?)(?<requestUriParameters>\?.+?)?\s+(?<protocol>.*?)"\s+(?<statusCode>.*?)\s+(?<transferredData>.*?)\s+"(?<referer>.*?)"\s+"(?<userAgent>.*?)"\s+(?<responseTime>.*)\s*/
# 複数のサービスがVirtual Host 等で一台に載ってる場合は下記で srcIpAddress を指定
# -------------------- ここから ------------------------------
#| filter srcIpAddress like /^search_your_service_domain/
# -------------------- ここまで ------------------------------
| filter statusCode = 200
| filter userAgent not like /(?:Googlebot|bingbot|GoogleStackdriverMonitoring-UptimeChecks|Zabbix|SemrushBot|Linespider|Amazonbot|GoogleDocs|Applebot|AhrefsBot|trendictionbot|ICC-Crawler|Hatena::Russia::Crawler)/
| filter srcIpAddress not like /^ip-\d+-\d+-\d+-\d+-\d+.ap-northeast-1.compute.internal/
| fields replace(replace(requestUriPath, '///', '/'), '//', '/') as requestUriPath_removed_double_slash
| fields replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(requestUriPath_removed_double_slash, '0', '*'), '1', '*'), '2', '*'), '3', '*'), '4', '*'), '5', '*'), '6', '*'), '7', '*'), '8', '*'), '9', '*') as requestUriPath_asterisk_array
| fields replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(requestUriPath_asterisk_array, '***********', '*'), '**********', '*'), '*********', '*'), '********', '*'), '*******', '*'), '******', '*'), '*****', '*'), '****', '*'), '***', '*'), '**', '*') as requestUriPath_uniq
| stats count(*) as uri_path_count, (avg(responseTime)/1000000.0) as avg_responseTime_seconds , (sum(responseTime)/1000000.0) as sum_responseTime_seconds  by httpMethod,requestUriPath_uniq
| sort by uri_path_count desc
| limit 10000

まとめ

この query を利用するサービスでは url 内の id が数字列であることが前提だったので、数字列を置換するだけで簡単にアクセス数の数え上げを行えました。
多分、id が文字列だとちゃんとしたログ解析ツールを利用したほうが早いと思います。
( replace 関数が正規表現に対応していないため )

簡素なログ解析として alp コマンドや athena 等の利用の利用をせず、 cloudwatch log insight だけでサービス内のアクセスを調べることができるということがわかりました。

ニッチな需要だと思いますが、Apache + cloudwatch logs を利用の方はクエリをお試し下さい。

Figmaユーザーにオススメ!ConfluenceにFigmaファイルを埋め込む方法

こんにちは!最近、就業型インターン生が加わり先輩感を噛み締めているデザイナーの鍜治本(@KAJIJI_Design)です!
インターン生に作業内容をまとめてもらった際に、Confluenceのページ上にFigmaのファイルを埋め込む方法を発見したので、備忘録の意味も込めて記事にしました!

ConfluenceにFigmaを表示できる発見

弊社では社内WikiことConfluenceを活用しており、仕様をまとめたりインフォメーションの共有などなど…、社員のナレッジにもなるように情報のストックをしています。 デザイナーも制作物の過程など基本的にはテキストでまとめて、必要があればキャプチャなど記載しているのですが、UIの画面類を説明する文章はなかなか難しい…

インターン生にサムネイルについて調べたことをまとめてもらっていた時のこと、「Figmaのショートカットとかあるのかな〜」と軽い気持ちで入力したところ…

あるじゃん。

しかも画面の埋め込みできるじゃん。
今まで必死に書いていたテキストとキャプチャたちは一体なんだったんだ…と思うほどの便利さ。

埋め込みでの表示方法

先の画像の通り「/figma」と入力して『Figma for Confluence(Canvas / Prototype / FigJam)』を選択します。 もしくは上部のメニューの+アイコンから検索しても良さそうですね。

入力するとどのファイルを表示するかやサイズの指定をするフォームが表示されます。Figmaの共有設定ダイアログから共有用のリンクをコピーし、Confluenceの一番初めのフォーム「Figma URL」にペーストすれば完了です! その際、ファイルにアクセスできるユーザーの設定を閲覧にしておくと良さそうです。(編集にするとedit権限のアカウントが増えてしまう可能性があるので注意しましょう)

ちなみに有料プラグイン

組織でConfluenceを導入しているため気づいていなかったのですが、なんと有料のプラグインでした…(すみません)
おおよそですが月額600円/(1〜10人)で、人数の規模が大きくなるほど価格が安くなっていくようです。
30日間の無料お試し期間があるようなので、ConfluenceとFigmaを活用している組織ならば導入を検討してもいいかもしれませんね…!
marketplace.atlassian.com

おわりに

デザイナーの活動をまとめるのはなかなか難しい部分もありますが、こういったツールを駆使して良くなるようにまとめていき、ナレッジ蓄積できるように努めたいです〜!🥺
最後になりますが、エキサイトではデザイナーの採用活動も行なっています!
カジュアル面談も行なっているので、ご興味のある方はぜひ🙇

【Azure Container Apps】リビジョンのアクティブ化 / 非アクティブ化を自動化した

はじめに

エキサイト株式会社 バックエンドエンジニアの山縣です。

我々のチームでは、Azure Container Appsを使用して、バックエンドサーバーを構築しています。 Azure Container Appsでは、主にリソース使用量によって課金されます。 そのため、開発環境やステージ環境など、常時稼働しないようなコンテナアプリでは、 使用しない深夜帯や休日に稼働を停止して、リソースを使用しないようにするのが望ましいです。

本記事では、GitHub Actionsを使用したリビジョンのアクティブ化 / 非アクティブ化について紹介します。

概要

Azure Container Appsのリビジョンは、レプリカ数が0だったり、非アクティブな状態だったりすれば、課金されることはありません。 そのため、Azure Container Appsを使用しない時間は、課金されないようにしたいです。 これを、azコマンドとGitHub Actionsのワークフローで実現します。

非アクティブなリビジョンには課金されません。 最大 100 個のリビジョンを持つことができ、その後は、最も古いリビジョンが消去されます。

docs.microsoft.com

注意点

本記事では、リビジョンモードを「単一リビジョンモード」に設定して、動作させています。 「複数リビジョン モード」では、うまく動かないことが想定されるため、注意してください。

定期実行

GitHub Actionsのscheduleイベントを使用して、定期実行のワークフローを作成ました。 深夜帯に課金されないために、21時に非アクティブ化、9時にアクティブ化をしています。

また、ワークフローを実際に作成するときには、以下の2点に注意してください。

  1. 指定した時間ピッタリには実行されない
  2. USTで時間指定する

docs.github.com

アクティブ化のワークフロー

--all--query '[?properties.trafficWeight == 100].[name]'オプションを指定して実行すると、 「重みが100のリビジョン名」を取得することができます。 これにより、「最後にアクティブ状態であったリビジョンをアクティブ化する」ことができるようになります。

アクティブ化のワークフローを次に示します。 コンテナアプリ名とリソースグループは、実際の値に置換してください。 また、Azureの認証で使用しているsecrets.AZURE_CREDENTIALSについては、次の記事を参考にしてください。

docs.microsoft.com

name: Activate

on:
  schedule:
    - cron: '0 0 * * 1-5'

jobs:
  activate:
    runs-on: ubuntu-latest
    env:
      AZURE_CONTAINER_APPS_NAME: sample-api
      AZURE_CONTAINER_APPS_RESOURCE_GROUP: sample

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Azure Login
        uses: azure/login@v1
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

      - name: Activate
        uses: Azure/cli@v1
        with:
          inlineScript: |
            az extension add --name containerapp --upgrade

            REVISION=$(az containerapp revision list \
                --name ${{ env.AZURE_CONTAINER_APPS_NAME }} \
                --resource-group ${{ env.AZURE_CONTAINER_APPS_RESOURCE_GROUP }} \
                --all \
                --output tsv \
                --query '[?properties.trafficWeight == `100`].[name]')

            az containerapp revision activate \
                --resource-group ${{ env.AZURE_CONTAINER_APPS_RESOURCE_GROUP }} \
                --revision ${REVISION}

非アクティブ化のワークフロー

--query '[].[name]オプションを指定して実行すると、「現在アクティブ状態のリビジョン名」を取得することができます。 これを使用して、非アクティブ化が行われています。

コンテナアプリ名とリソースグループは、実際の値に置換してください。

name: Deactivate

on:
  schedule:
    - cron: '0 12 * * 1-5'

jobs:
  deactivate:
    runs-on: ubuntu-latest
    env:
      AZURE_CONTAINER_APPS_NAME: sample-api
      AZURE_CONTAINER_APPS_RESOURCE_GROUP: sample

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Azure Login
        uses: azure/login@v1
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

      - name: Deactivate
        uses: Azure/cli@v1
        with:
          inlineScript: |
            az extension add --name containerapp --upgrade

            REVISION=$(az containerapp revision list \
                --name ${{ env.AZURE_CONTAINER_APPS_NAME }} \
                --resource-group ${{ env.AZURE_CONTAINER_APPS_RESOURCE_GROUP }} \
                --output tsv \
                --query '[].[name]')

            az containerapp revision deactivate \
                --resource-group ${{ env.AZURE_CONTAINER_APPS_RESOURCE_GROUP }} \
                --revision ${REVISION}

おわりに

本記事では、GitHub Actionsを使用したリビジョンのアクティブ化 / 非アクティブ化について紹介しました。 Azure Container Appsの導入事例は少ないため、公式ドキュメントを読み込んで、手を動かして試す日々が続いていました。 本記事で取り上げた手法がお役に立てれば幸いです。

採用アナウンス

エキサイトではフロントエンジニア、バックエンドエンジニア、アプリエンジニアを随時募集しています。 また、長期インターンも歓迎しています。

カジュアル面談からもOKです。少しでもご興味がございましたら、お気軽にご連絡頂ければ幸いです。

▼ 募集職種一覧 ▼ www.wantedly.com

GiNZAを使って形態素解析して遊んで見る

こんにちは。
いつものtaanatsuです。

しばらく自然言語処理の世界から離れていたので、
リハビリがてら使ったことのない「GiNZA」を使って形態素解析をして遊んでみます。
(ずっとMeCabユーザでした)

準備

venvを作っておきます

$ python3 -m venv venv

venvのactivate

$ source venv/bin/activate

Windowsの場合は venv¥Scripts¥activate.batで行けると思います。
※ →参考

GiNZAを入れる

$ pip install -U ginza ja_ginza_electra

GiNZAを実行する

$ ginza

入力待ちになるので、文字を入力します。

GiNZAで形態素解析

ここまでサクッとエラーなく動くのはいいですね!

次回はPythonプログラムで動かして遊んでみようと思います。
今回はここまで!
ではまた次回〜

ECS Fargate Spotの中断時に、安全にALBから登録解除する方法

こんにちは。 エキサイト株式会社の三浦です。

Amazon ECS でFargateを使用する際、通常のFargateとは別にFargate Spotを選択することが出来ます。

Fargate Spotは通常のFargateに比べて非常に安く、ぜひとも使っていきたい一方で、AWS側の都合でコンテナが中断されてしまう恐れがあります。

そのため、何も考慮せずにALBに接続してWebアプリケーションとして使っていると、コンテナ中断のタイミングでリクエスト元に対してエラーが返されてしまう原因になります。

今回は、Fargate SpotをALBと接続して使用するときに、Fargate Spotの中断に際して安全にALBから解除する方法を説明します。

Fargate Spotと中断

Fargate Spotは、格安でFargateを使用できる代わりに、AWSの都合のタイミングでコンテナの中断が起こりうるサービスです。

docs.aws.amazon.com

Fargate Spotを使用すると、割り込み許容のあるAmazon ECSタスクを、Fargate料金と比較して割引料金で実行できます。Fargate Spot は、予備のコンピュートキャパシティーでタスクを実行します。AWSキャパシティーを戻す必要がある場合、タスクは中断され、2 分間の警告が表示されます。

aws.amazon.com

Fargate Spot では、割り込み耐性のある Amazon ECS タスク* を予備の容量で実行することができ、通常の Fargate 料金の最大 70% 割引で購入することができます。

コストの観点から言えば可能な限りSpotを使いたいところですが、FargateにALBを接続してWebアプリケーションとして使っているサービスの場合、コンテナが中断されたタイミングでリクエスト元に対してエラーを返してしまう恐れがあるという問題があります。

コンテナ中断対策

この問題はAWSも認識しており、以下のブログでその対処方法を説明しています。

aws.amazon.com

FARGATE_SPOT として実行されるタスクでは、ロードバランサーのターゲットグループから登録解除されてからタスクが STOPPED 状態に移行するという保証はありません。望ましくないエラーを回避するために、必要な API を呼び出して、ロードバランサーのターゲットグループからタスクを登録解除することをお勧めします。


FARGATE_SPOT タスクが終了としてマークされると、タスク内のすべてのコンテナがすぐに SIGTERM シグナルを受信します。SIGTERM を受信したタイミングで、登録解除の遅延期間が満了するまでシグナルハンドラをスリープさせておくのも一つのアイデアです。

端的に書くと、

  • Fargate Spotの中断イベントが発生したタイミングで、ALBから対象コンテナの登録を解除する
  • 中断イベントが発生するとコンテナにSIGTERMが送られるが、それを受け取ったらシグナルハンドラをスリープさせる

という対応が必要、ということになります。

こちら特に後半がミソで、これを「SIGTERMを受け取ったらちゃんとGraceful Shutdownが起きるようにする」という形で対応すると問題が起こりえます

(以前以下の記事で「Graceful Shutdownにすれば問題ない」と書きましたが、ALBと接続している場合ではエラーが起きてしまうケースがありました。)

tech.excite.co.jp

というのも、上記の対応をしても、順番的に

  1. ALBからコンテナが登録解除される
  2. 解除後、SIGTERMがコンテナに送信される

という順番が保証されていないようで、

  1. SIGTERMがコンテナに送信される
  2. ALBからコンテナが登録解除される

という順番になる可能性もあるようなのです。

そうなった場合、SIGTERMの送信によってコンテナのアプリケーションがGraceful Shutdownに入る、すなわち新規のアクセスを受け取らない状態になっているにも関わらず、ALBからはまだ新規のアクセスが送られてくるために、その間の新規アクセスにはエラーが返されてしまいます。

ここで必要なのは、「SIGTERMが送られてきたらGraceful Shutdownにする」ではなく、「SIGTERMが送られてきても無視する(もしくはそれによる処理を遅延させる)」ということになります。

こうすることで、アクセス制御がALBのみに委ねられることになり、ALBが適切に新規アクセスを中断が起きていないコンテナに割り振ってくれるため、リクエスト元にエラーが発生しません。

SIGTERMの無視には、例えば以下のような方法があります。

tech.excite.co.jp

注意点として、SIGTERMでコンテナが終了しないため、コンテナが終了するタイミングはSIGTERM送信の指定時間後に発生するSIGKILLの送信による強制終了になります。

そのため、SIGKILLによる強制終了でも問題なくアプリケーションが終了するように調整する必要があります。

また、ALBからの登録解除が終了する前にコンテナが強制終了してしまわないよう、stopTimeout(SIGTERMが送られた何秒後にSIGKILLが送られるかの設定値)を適切に設定しておく必要があります。

docs.aws.amazon.com

最後に

そのWebアプリケーションに対してアクセスが多ければ多いほど、Fargate Spotの中断による影響範囲は大きくなります。

Spotの中断自体がそこまで頻繁に起きるわけではないので軽視する場合もあるかもしれませんが、上記のように適切に対処すればエラーも起きなくなるので、ぜひ設定してみてください。

Thymeleafを使ってheaderとheadとfooterの共通化

こんばんは、エキサイト株式会社の中尾です。

今回はThymeleafを使ったheaderとheadとfooterの共通化を説明します。

公式documentに書いている通りですが、記載します。

www.thymeleaf.org

まず、gradleに以下をimplementationします。

    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:2.7.2'

index.htmlhead.htmlheader.html を作成します。

th:replace を呼び出す側からfileを指定(htmlを省略)、呼び出される側に th:fragment で値を受け取れるようにします。

以下、具体的なhtmlファイルです。

index.html

<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head th:replace="common/head :: head_fragment(title = 'ユーザ検索', scripts = ~{}, links = ~{})"></head>
<!-- index.html専用のjs,cssがないので {} を空で渡す-->
<body>

<div th:replace="common/header :: header_fragment"></div>

<div class="container-fluid">
    <div class="row">
<!-- main コンテンツはここから -->
        <span>hello world</span>
<!-- main コンテンツはここまで -->
    </div>
</div>

</body>
</html>

head.html

<head xmlns:th="http://www.thymeleaf.org" th:fragment="head_fragment(title, scripts, links)">
    <title th:text="${title}"></title>
    <!-- Bootstrap core CSS -->
    <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
    <!-- 各画面専用のscript,cssを定義します -->
    <th:block th:replace="${links}" />
    <th:block th:replace="${scripts}" />
</head>

header.html

<header xmlns:th="http://www.thymeleaf.org" th:fragment="header_fragment">
  サンプルアプリケーションのヘッダーです。
</header>

上記のhtmlを用意して、実行してアクセスします。 すると、htmlが以下のように出力されるはずです。

<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
    <title>ユーザ検索</title>
    <!-- Bootstrap core CSS -->
    <link href="/css/bootstrap.min.css" rel="stylesheet">
    <!-- 各画面専用のscript,cssを定義します -->
<body>

<header xmlns:th="http://www.thymeleaf.org">
  サンプルアプリケーションのヘッダーです。
</header>

<div class="container-fluid">
    <div class="row">
<!-- main コンテンツはここから -->
        <span>hello world</span>
<!-- main コンテンツはここまで -->
    </div>
</div>

</body>
</html>

th:replace が別に分けてたhtmlに置き換わっていることがわかります。

Thymeleaf 3.0では推奨されなくなりましたが th:include もあります。

【GitHub Actions】JSON / XML / YAMLファイルを動的に書き換える方法

はじめに

エキサイト株式会社 バックエンドエンジニアの山縣です。

GitHub Actionsを使用して、 ワークフローを実行するときに、YAMLファイルを動的に書き変える必要がありました。 本記事では、デプロイ周りを整備するときに得た知見の1つとして、GitHub ActionsでJSON / XML / YAMLファイルを動的に書き換える方法について紹介します。

やること

サンプルとして、次のJSONファイル(sample.json)を用意しました。

{
    "value": "",
    "items": [
        {
            "name": "",
            "price": 100
        },
        {
            "name": "",
            "price": 200
        }
    ]
}

GitHub Actionsを実行時に、value, items[0].name, items[1].nameを、次のとおりに書き換えます。

{
    "value": 123,
    "items": [
        {
            "name": "apple",
            "price": 100
        },
        {
            "name": "orange",
            "price": 200
        }
    ]
}

ワークフロー

GitHub ActionsでJSONファイルを動的に書き換えるために、microsoft/variable-substitution@v1を使用します。 これを使用すると、JSON / XML / YAMLファイルにある値を、動的に書き換えることができるようになります。

github.com

使用方法

サンプルとして、「GitHubにプッシュ時に、JSONファイルの値を書き換えて、それを表示する」ワークフロー(sample.yml)を用意しました。 これを、.github/workflowsディレクトリ配下に置いてプッシュすることで、ワークフローを実行することができます。

on: [push]
name: run

jobs:
  run:
    runs-on: ubuntu-latest

    env:
      ITEM_NAME_1: orange

    steps:
    - uses: actions/checkout@v2

    - uses: microsoft/variable-substitution@v1 
      with:
        files: sample.json
      env:
        value: 123
        items.0.name: apple
        items.1.name: ${{ env.ITEM_NAME_1 }}
    
    - run: cat sample.json

配列の要素にアクセスする

ドキュメントには、配列の要素にアクセスする方法について書かれていません。 しかし、Issueを見てみると、インデックスを指定すると配列の要素にアクセスできることがわかります。

配列の要素にアクセスするときは、items.0.nameのように記述する必要があります。

github.com

実行結果

実際に、上記のワークフローを実行します。 確かに、value, items[0].name, items[1].nameが書き換わっていることが確認できます。

おわりに

microsoft/variable-substitution@v1を使用することで、YAMLファイルを動的に書き換えることができました。 これにより、次のようなことができるようになります。

  • シークレット情報を環境変数に置き、JSONで作成した設定ファイルに渡すことができる
  • コミットハッシュ値を使用した文字列を、YAMLで作成した設定ファイルに渡すことができる

この記事がお役に立てれば幸いです。

採用アナウンス

エキサイトではフロントエンジニア、バックエンドエンジニア、アプリエンジニアを随時募集しています。 また、長期インターンも歓迎しています。

カジュアル面談からもOKです。少しでもご興味がございましたら、お気軽にご連絡頂ければ幸いです。

▼ 募集職種一覧 ▼ www.wantedly.com

テーブル構成を考える時に重視すべき点

エキサイト株式会社のAです。 今回はデータベースの構成を考える上で、特に重視すべき点を事例を用いて紹介します。

アクティブか否かのフラグや表示非表示の状態を極力カラムで管理しない

事例として、enabled_flgという記事データが有効か無効かを表すフラグがカラムに存在していました。 しかし、後から有効無効以外にも「許可非許可」以外にも「未承認」の状態が欲しいと言った要件が発生しました。

その時には既にテーブルのデータが増えすぎておりカラムの追加も容易にできなくなっていたため、 当時の担当はenabled_flgカラムを「0:未承認 1:許可 -1:非許可」と言った状態にしてフラグ管理カラムに複数の意味を持たせてしまい 非常に運用もしづらく、分かりづらい作りになっていました。

1テーブルに載せるカラム数は極力少なくしておかないと、後々テーブルのデータが肥大化して行った時に データ量が多すぎて簡単にalter tableを流すことができない状態にもなりかねません。

カラムを追加しすぎて肥大化したテーブルの例

これを防ぐためにはカラムにenabled_flg や active_flgなどは極力付けずに、activeテーブルなどを用意してテーブル別に管理するようにします。 上記の事例で言うと、表示非表示はactiveテーブルで管理しつつ、承認か未承認かの状態も承認用の管理テーブルを用意します。

そうすることで一つ一つのテーブル構成がシンプルになり、新たな要件が来た時も簡単に拡張を行うことができます。 また、テーブルのカラムに複数の意味を持つことがなくなるため、後に他の人が見たとしても理解しやすくなります。

注意点としてはテーブルの数が多くなりがちなため、しっかりと命名規則を決め見やすくする必要があることと、 JOINのコストは発生するためINDEXの付け方や各テーブルにいれるカラム数などはケースに合わせて工夫する必要はあります。

コメントを必ずつける

当たり前のことではありますが、テーブルを作る際に各テーブルがどのようなテーブルなのか、各カラムがどのようなカラムなのかを必ず記入しましょう。 コメントがないと後々DBの運用する人が変わった際に、どのカラムが何に使われているか分からなくなることが多くなります。

上記を踏まえた例

CREATE TABLE `information_seq` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `info_code` varchar NOT NULL,
  `infoday` date NOT NULL,
  `infoseq` tinyint(4) unsigned NOT NULL DEFAULT '1',
  `sort_order` tinyint(1) unsigned NOT NULL DEFAULT '1',
  `version` tinyint(1) unsigned NOT NULL,
  `opt_var1` varchar(10) DEFAULT NULL,
  `opt_var2` varchar(10) DEFAULT NULL,
  `opt_int1` int(10) unsigned DEFAULT NULL,
  `opt_int2` int(10) unsigned DEFAULT NULL,
  `insert_time` datetime NOT NULL,
  `update_time` datetime NOT NULL,
  `active_flg` tinyint(1) unsigned NOT NULL DEFAULT '1',
...

上記の例で言うと、infodayinfoseqがどのようなデータなのかこれを見ただけでは全く検討もつきません。 また、opt_var1opt_int1と言った状態もカラムで持ってしまっており、尚且つコメントもないためこちらもどのようなデータかわかりません。 そのためコメントは必ず追加し、状態を持つ場合はテーブルを分けることで非常にシンプルになり機能の実装や改修が早くなります。

終わりに

コメントなどは惰性で書いたりして軽視しがちですが、プロジェクトに新規で人が入った場合などでも明らかに開発効率がよくなるため、シンプルなDB構成は重要です。

quarkusを使う(コンパイル編) - buildに成功?

こんばんは、エキサイト株式会社の中尾です。

quarkusを使う(コンパイル編) - エキサイト TechBlog.

でcompileできなかったところがなんとcompileできるようになっていました!

どうやらissueを見るとGraalVM 22.1 を使うようになったのでそもそもできるみたいですね!

https://quay.io/repository/quarkus/ubi-quarkus-mandrel?tab=tags&tag=21.3-java17 https://github.com/quarkusio/quarkus/issues/20891

正式な使い方はguidesを参考にすればできそうです!

quarkus.io

ちなみにguides通りに quay.io/quarkus/ubi-quarkus-mandrel:21.3-java17 のイメージを使えばbuildできるところまでは確認しました!

次回、起動まで試してみようと思います!