tekitoumemo’s diary

思ったことを書くだけ。長文版Twitter

macでAzure CLI入れてApp Serviceにsshするまで

App ServiceはPaasなのでsshを使う意味はほとんどありませんが、検証する際にたまーに必要になったりします。Azure portalから簡易的なsshが使えるので、必要はないのですが、芸がないので一応やってみることにしました。

HomebrewでAzure Cliをインストール

brew update && brew install azure-cli

バージョン確認

az --version
azure-cli                         2.0.77

command-modules-nspkg              2.0.3
core                              2.0.77
nspkg                              3.0.4
telemetry                          1.0.4

Python location '/usr/local/Cellar/azure-cli/2.0.77/libexec/bin/python'
Extensions directory '/Users/saitouikuo/.azure/cliextensions'

Python (Darwin) 3.7.5 (default, Nov  1 2019, 02:16:23)
[Clang 11.0.0 (clang-1100.0.33.8)]

Legal docs and information: aka.ms/AzureCliLegal



Your CLI is up-to-date.

ログイン

az loginするとブラウザが立ち上がってログイン画面が開くのでazure portalに入るIDとパスを入力して下さい。

az login
You have logged in. Now let us find all the subscriptions to which you have access...
[
  {
    "cloudName": "AzureCloud",
    "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "isDefault": true,
    "name": "xx課金",
    "state": "Enabled",
    "tenantId": "xxxxxxx",
    "user": {
      "name": "hoge@hoge.com",
      "type": "user"
    }
  }
]

アプリへのリモート接続を開く

subscription-idはログイン時に表示されたidの値、resource-group-nameは接続したいAppServiceのリソースグループ名、app-nameはAppService名です。

az webapp create-remote-connection --subscription <subscription-id> --resource-group <resource-group-name> -n <app-name> &
Port 21382 is open
SSH is available { username: root, password: Docker! }
Start your favorite client and connect to port 21382

そうするとポート21382で接続する準備が出来ました。

sshセッションを開く

先ほど表示されたポート番号を指定します。

ssh root@127.0.0.1 -p 21382
The authenticity of host '[127.0.0.1]:21382 ([127.0.0.1]:21382)' can't be established.
ECDSA key fingerprint is xxxxxxxxxxxxxxxx
Are you sure you want to continue connecting (yes/no)?

ここまで表示されたらyesを入力し、先ほど表示されたパスワードDocker!を入力します。

  _____
  /  _  \ __________ _________   ____
 /  /_\  \___   /  |  \_  __ \_/ __ \
/    |    \/    /|  |  /|  | \/\  ___/
\____|__  /_____ \____/ |__|    \___  >
        \/      \/                  \/
A P P   S E R V I C E   O N   L I N U X

Documentation: http://aka.ms/webapp-linux
Dotnet quickstart: https://aka.ms/dotnet-qs

完了。

Azure App Serviceでは.NET Coreの拡張子なしの静的ファイルにアクセス出来ない?

誰の役に立つか不明だが一応。

個人的な都合でLet's Encryptの発行をubuntu機で行っていました。ちょうど入院(え?)したこの機会にmacで発行しようといろいろ試した備忘録です。

いままではLet's EncryptをDNS認証で行っていましたが、今回はACMEチェレンジで試してみました。このチェレンジをするにあたって拡張子なしの静的ファイルにアクセスしなければいけないのでいろいろ思考錯誤しました。

certbotインストール

brew install certbot
certbot --version
certbot 0.39.0

発行申請を出す

sudo certbot certonly --manual --domain example.com
...
Create a file containing just this data:

xxxxxxxxxxxx

And make it available on your web server at this URL:

http://example.com/.well-known/acme-challenge/yyyyy

.well-known/acme-challenge/yyyyyxxxxxxxxxxxxという内容でファイルを置く必要があります。.NET Coreだとセキュリティ上、静的ファイルを気軽に置けません。.NET Coreでは管理外のMINEタイプにアクセスすると404を返す仕様になっています。なので、特定のファイルでもアクセスできるように設定しなければいけません。

StaticFileOptionsのServeUnknownFileTypesプロパティを有効にする

これはすべての静的ファイルが有効になるので非常に危険です。まずやらないほうが良いです。

app.UseStaticFiles(new StaticFileOptions
{
    ServeUnknownFileTypes = true,
});

ローカルでは静的ファイルを返すようになりました。ちなみにホスティングにApp Serviceを使っているのでデプロイしたら静的ファイルにアクセス出来ませんでした。

特定のパスだけOKにする

StaticFilesがファイルの種類を取得する場合にIContentTypeProviderを使っているのでIContentTypeProviderを実装して特定のパスだけOKにする方法。

public class LetsEncryptWellKnownContentTypeProvider : IContentTypeProvider
{
    private IContentTypeProvider _baseProvider;

    public LetsEncryptWellKnownContentTypeProvider()
        : this(new FileExtensionContentTypeProvider())
    { }

    public LetsEncryptWellKnownContentTypeProvider(IContentTypeProvider baseProvider)
    {
        _baseProvider = baseProvider;
    }

    public bool TryGetContentType(string subpath, out string contentType)
    {
        if (subpath.StartsWith("/.well-known/acme-challenge/"))
        {
            return true;
        }

        return _baseProvider.TryGetContentType(subpath, out contentType);
    }
}

で実装後は登録。

app.UseStaticFiles(new StaticFileOptions
{
    ContentTypeProvider = new LetsEncryptWellKnownContentTypeProvider()
});

ローカルは静的ファイルにアクセス出来たのに、App Serviceでは相変わらずだめでした。

Azure App ServiceではCore上の静的ファイルにアクセス出来ない!?

よくわかりませんが、あえてやる必要もないので諦めました。
stackoverflow.com

DNS認証が楽

certbot certonly --manual -d example.com --preferred-challenges dns

DNSが登録されたことを確認出来たら終了。

運営してるサービスのパフォーマンス改善をしました

こんにちは。

みんなの洋楽ランキングでパフォーマンス改善をした結果、かなり速度が改善されたのでやったことを記載します。

まずは結果から

計測はPageSpeed Insightsで行っています。この結果はモバイルのみであり、デスクトップの結果は面倒なので載せません(計測したら95でした)

改善前

f:id:tekitoumemo:20191007023730p:plain
そもそもドメインが違っていてすみません。改善前のキャプチャを撮るのを忘れてしまったので違うドメインで作り直しました。これも改善後なので実際は35とかだった気がします。

改善後

f:id:tekitoumemo:20191007021415p:plain
結構改善されてますね。まだまだ伸ばしたかったのですが、大幅な構造変更が必要だったため、ここらへんで落とし所を付けました。

まずはどこまで改善できるか知る

まずはどこまで改善できるのか知る必要があります。普通に考えて100点は不可能なので、自分のサイトでどれだけの速度が出せるのか限界値を知り、その数字を目標に改善していくのが良いと思います。利用規約は画像も処理もないので限界値を知る良い指標になるかと思います。
f:id:tekitoumemo:20191007030140p:plain

最高で71点まで伸ばせることがわかりました。

なにをやったか

いろいろやりましたが、うまく行ったところと効果が薄かった改善があります。結果としてやってよかったことばかりなのでどちらも記載していきます。

サーバーサイドリファクタ(効果無)

ここではn+1、不要なインスタンスが生成されてないかなど中心に見ていきました。もともとデータアクセス周りの設計はちゃんとやっていたこともあり、パフォーマンスとは関係ないリファクタを中心にやりました。
f:id:tekitoumemo:20191007032621p:plain

WEBパフォーマンスはほとんどフロントにあると思うので、サーバーの改善はあまり効果が出ない可能性がありコスパ悪いです。

不要なスタイル、JSを削除する(効果少)

みんなの洋楽ランキングはとあるHTMLテンプレートをベースに作ったので、不要なスタイルやJSが大量にありました。明らかに使ってないファイルが大量にあったので削除→確認→削除のループでひたすら消していきました。とんでもない量の不要なファイルがありました。。
f:id:tekitoumemo:20191007031440p:plain

こちらは1~3点ぐらいしか変わらず、労力の割には効果が得られる結果になりませんでした。でもこれがあとに効いてくるんです!

画像を圧縮する(効果中)

PageSpeed Insightsは画像に関してはかなり辛めです。改善前はこんな感じです。
f:id:tekitoumemo:20191007180443p:plain
画像はImageOptimを使っていて、かなり厳しい圧縮をしているのですがまだ減らせと言うてきます。
f:id:tekitoumemo:20191007180746p:plain
限界まで圧縮してんの204 KB→147 KBとか無理やろ。とか思いながら調べたら表示しているサイズに対して画像がでかすぎることが原因でした。こちらは画像をリサイズして圧縮したら改善されました。やはり画像はでかいのでポイントはちょっと上がります。5~8点ぐらい変わったかな?

テキスト圧縮の有効化(効果大)

テキストが未圧縮のままだったのでgzip形式に圧縮しました。PageSpeed Insightsでも一番影響がある部分と示されてました。
f:id:tekitoumemo:20191007181933p:plain
.Net Coreでのgzip化は以下の通りです。

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    ...
    // Configure Compression level
    services.Configure<GzipCompressionProviderOptions>(options => options.Level = CompressionLevel.Fastest);

    // Add Response compression services
    services.AddResponseCompression(options =>
    {
        options.Providers.Add<GzipCompressionProvider>();
        options.EnableForHttps = true;
    })
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseResponseCompression();
}

詳しくはこちら

静的ファイルのキャッシュ(効果大)

なんとなくキャッシュの影響が大きい気がしてた(調べるのサボってた)ので実施していませんでしたが、この機会に導入しました。
f:id:tekitoumemo:20191007185125p:plain
ほとんど画像がかわることがないことに加えてasp-append-version="true"を導入しているので2週間に設定しました。5点ぐらい上がった気がします。

AMP化(失敗)

そもそもAMPとは?

AMP(Accelerated Mobile Pages)とはGoogleTwitterで共同開発されている、モバイル端末でウェブページを高速表示するためのプロジェクト、またはそのためのフレームワーク(AMP HTML)のことです。

画像のLazy Loadをするためにamp-imgを使いたかったのですが、みんなの洋楽ランキングでは失敗しました。失敗した理由としては、とあるHTMLテンプレートで開発した背景があり、Masonryというライブラリを使っています。これが悪さをして、画像がロードしたあとに実行されます。そうするとこんな感じになってしまい、うまく機能しませんでした。デザインを大幅に変更したい気持ちはあるのですが、効果に対して労力が大きすぎます。今の所、そこまで困ってないのでこれは諦めました。HTMLテンプレート使う場合は、可能な限りライブラリを使ってないやつを選びましょう。。
f:id:tekitoumemo:20191007183659p:plain
毒吐きまくってます笑

PageSpeed Insightsでは、AMP化してもhttps://cdn.ampproject.org/v0.jsが大きすぎて数値が変わらないという結果になりました。でもasyncで読み込んでるし結果として導入したほうが絶対よいです。

やってないこと
  • 使ってないCSS、JSを削除
  • 画面デザイン変更

まず、CSS、JSの削除は画面に必要なもののみ読み込まなきゃいけないので割にあわないです。数十キロバイト程度なのでここは諦めても良いかなぁと思ってます。画面デザインの変更は、突き詰めればやったほうが良いですがページ速度はほとんど関係がないのでここも諦めました

感想

パフォーマンスはSEOに影響があると言われてますが、上位に来るサイトはあまり点数が良くないので実は関係ないんじゃないか?と思ってます。Speed Updateとか言ってるけどほんとに?って感じです。SEO対策としてパフォーマンスを上げるのは効率が悪いと思うのでここはUXの向上やリファクタリングと組み合わせて実施する方が気持ち的にも楽かなと思います。ちなみにアマゾンはモバイル60点台のPC100点なのでここら辺を維持出来たら完璧かと思います。

アマゾンマジすげーや

食洗機が最強すぎた

今までいろいろ購入して便利だったものを書きました。

tekitoumemo.hatenablog.com
tekitoumemo.hatenablog.com

乗り物ばっかやんけ!!

僕も昔と比べて大人になりました。もう結婚もして子どももいます。なのでもう少し実用的なものを紹介します。

食洗機!!

やばい、まじやばい。買わない意味がわからない。一人暮らしでも買う価値ありまくりで生活が鬼のように変わった。

買ったやつはこれ

ヤマダ電気で値切って6万ぐらい。工事費含めると7万ちょいで付けられる。

良かったこと

これ。適当に皿を水で流して食洗機に突っ込むだけ。今の食洗機は水で流す必要すらないと思いますが、手を洗うついで的な感じで。手洗いの場合は水に付けて洗剤スポンジにつけてゴシゴシして空いてるとこに食器おいて水で流す。ヌメってたらまた洗い直す。アホやろ。

静か

音は鳴るけど、深夜でも使えるレベル。洗濯機に比べたらすごく静か。

めっちゃ汚れ落ちる

手洗いより全然落ちる。フライパンも一撃。食洗機用の洗剤は強力で手洗い用の洗剤とは格が違います。手洗い用の洗剤は食品衛生法の管理化にあるらしく、野菜や手荒れなどを考慮した基準で作られてるから弱いらしい。つまり、ゴシゴシはまじで無駄だったってこと。

乾燥が最強

洗って乾燥すればあとは食器棚に入れるだけ。洗うだけでもダルすぎた作業に加えて食器を拭かなきゃいけないとか辛すぎ。電気代も対して変わらないのでゴリゴリ使っても良いかも。

後先考えずに料理ができる

これかなりメリットだと思う。何品か料理作ると同時に皿も増えるので結果皿洗いが増える。妥協するとバランスだったり食事が貧相になるので何も考えずに料理作れるのは良いことだと思う

夫婦仲がよくなる

当番制、仕事が楽な方などどちらかが辛い思いをしなければいけないのですが、食洗機だとどっちがやってもいいレベルで楽です。最近は子育てが辛い嫁を気遣って僕がやるようにしてますが、それだけで感謝されるので最強。

無駄な水を使わない

らしい。

悪かったこと

でかい

水切りカゴを置く場所がなくなりました。なくなっても良かったので結果おけ

たまに手洗いが必要

でかいフライパンとかしゃーない。

ピー音が冷蔵庫の開けっ放しの音と似てる

うちだけかもしれないが、食器洗いが完了するとピー音がなってビビる。

感想

はよ買えばよかった

個人サービスでの設計について

みんなの洋楽ランキングを一年ちょい運用してみて、ある程度それなりの設計をしてるので書き溜めてみる

一応ドメイン駆動設計に基づいて作ってる気がするのですが、そこで疲弊するのが辛いからわりかし適当、というか設計わかりません。個人サービスだと開発に関わる人間が基本一人なのである程度複雑でも対応出来るし、複雑な設計してもサービスを展開するスピードが遅くなるだけなのであえてある程度適当にしてるって言う意図があります(何も考えたくないだけ

構成は

サーバー: centos
サーバーサイド: ASP.NET Core
フロント:react
フレームワーク:MVC

Controller

リクエストをアプリケーションサービスに渡す、返すだけ。まじなんもしてない。たまにリクエストヘッダーとかセッションとかここで使う

Dependency Injection

ASP.NET CoreからDIアーキテクチャが導入されたので全てのクラスをインターフェースを介して使ってる。

Repository

データの永続化を担うとこ。要はデータアクセスの場所。複雑なクエリはアプリケーション側の都合だと思ってるから本当に単純なデータを引っ張ってくるとこ。ここはDapper早いしLinq強いから出来ることだと思う。インピーダンスミスマッチとか気にせずSQLゴリゴリに書いてる。

Model

スキーマと全く同じ構成のクラス群。Dapperというライブラリを使っていて、複雑なエンティティ(集約)を作ると辛くなるのでテーブルとクラスが一対一になっている。多分C#だったらこれはあるんじゃないかな?

ViewModel

UIにゴリゴリ依存したクラス群。Viewsと対になってる。

Middleware

.NET CoreではDIするためのインスタンスをStartupってところで作るのですが、ゴリゴリにでかくなるのでちょっと分けちゃったりしたりするとこ。一般的にはinfrastructureって呼ばれるとこかな

Views

ビューのレンダリングはrazorって言うASP.NETレンダリングエンジンってやってる。そのHTMLの置き場。

ResponseModel

react用のREST APIがいくつかあってそのレスポンスの型定義。名前とか終わってる気がするけど「ビューモデル」があるからオッケーってノリで

Extensions

C#には拡張メソッドって言う天才的なものがあって型にオリジナールのメソッドを作れる。日付変換などアプリケーションの仕様的なものはここに詰められる。ほかの言語だとfunctionとかかな?(その名前も大概だけど)

Constant

定数置き場。普通はDomainとかなのかな?

ClientApp

reactコンポーネント置き場。dotnet cliで生成された名前。

Attribute

CustomAttributeの置き場。

Manager

名前微妙ですが、「外部との連携するために独立して動くもの」って感じのやつが入ってる。Twitterライブラリのラッパーだったりアナリティクスラッパー、メール送信等々。azure functionから使ったりwebサービスから使ったりと共通して使えてかつ依存しないものを詰め込んだもの。実際は別リポジトリで管理してsubmoduleして使ってる。よくありがちな抽象的な名前にしたら何でも屋になった的な感じ。HTTP Clientはrepositryだろと思うけど、アプリケーションデータの核となる部分と認識しちゃってたからデータアクセスやらに限定してた。

Service

ビューモデルやResponseModelを作るところ。アプリケーションがどういった振る舞いをするかはここみれば大丈夫だろうってところ。でかくなりがちでいろいろやり過ぎてる感満載。複雑のデータの取得をしないように心掛けてるからどうしてモデルの整形などはここに集中してしまう。規模に応じてアプリケーションサービスとドメインサービスを分ける予定。

library

自前のライブラリ群。自作してだっぱーのらっぱーとか入れてる

Styles

共通して使うスタイル、画面特有のスタイルとざっくり分けてる。ここら辺はほんと適当。画面特有のスタイルなんてほとんどないから共通系が肥大化して「あれ?意味なくね?」ってなってる。

Images

画像とか思いっきりまとめてる。そこまで数が無かったんだけどちょっと増えてきたから検討中

db

ddldmlがあるとこ。C#に良いマイグレーションツールがない。ほんとdb系は弱いなぁという印象。

Test

まんまテスト。ちょーざっくりしたインテグレーションテストしかやってないけど

運用してみて

あいまいな名前(Service、Managerなど)は何でも屋になりやすいし、ゴミ箱(なんでも詰められる)になりがち。設計は抽象度が保たれた方が良いと思うけど、言語特有の分け方した方が、言語に精通してる人にはわかりやすい気がする。デカくなったら分ける、これでいいと思う。DIは最強、このおかげである程度依存が切り離せてるから素敵。でもどこでもDIはよくない。どうしてもN+1を考慮した作りをすると複雑で分かりにくいからここは課題。やっぱドメインサービスつくるかぁ。図を作った方が良いなこれは

SEOの結果が出るまで

一年ちょい運用してたみんなの洋楽ランキングが望んだキーワードで上位10位に入りました。

まぁ、結局SEOなんてものは気まぐれだし思うようにコントロールなんか出来ないので、ポエムってるところが多いがご了承下さい

狙ったキーワード

洋楽ランキング
洋楽 ランキング

検索ボリューム

15〜20万

現状のアクセス数

だいたい月7000〜8000PVってとこ。ユーザー数は1500ぐらい。今月はSEOの威力で15000PVぐらいで留まりそう。

対策

ここに書いてあるやつはだいたいやったかも。ここら頑張ったとこですぐには結果は出ないので、折れない心が大事。流入少なすぎて個人サービスクローズする人がめっちゃ多い気がする。とにかくコンテンツを強化するのに徹した。

記事数

多分1000以上

ブログじゃないんだけど、解説が誰でも書けるので「記事数」って呼んでる。7〜8割が人が書いた記事で残りはwikiを引用してます(wikiも人間が書いてるから100%人が書いたって言っても問題無いけど)wikiはちゃんと明記したら引用出来るよ!

時系列

だいたいここに書いたが、ざっくり説明。

・リリース月
ほぼ流入なし。
・3ヶ月目
SEOで4位に。月2万PVまで伸びる。
・半年
圏外に落ちる。月4000PVぐらいになる。Googleネムーンって本当にあるんだなーと実感。
・10ヶ月
会員登録実装して誰でも投稿出来るようにした結果、月一万PV超え。相変わらずSEOは絶不調で50位ぐらいに。
・今
先月あたりから20位〜10位あたりを行き来。万越えは確実に

考察


正直

よくわかんない

わからないなりに分析すると

サイトに価値を高めることが重要

キーワードに対してその価値を提供出来てるのかがめっちゃ重要だと思う。まぁ、だいたいこんな感じのことはどこでも書いてあって「知りたいのはそこじゃねぇ!」ってなるんだけど、具体的に、直帰率離脱率滞在時間かなと。普通に考えて「洋楽ランキング」とか調べて「演歌」のページにアクセスしたら2秒で直帰するよね。もうその「演歌」のページは「洋楽ランキング」としての価値はないのでSEOが下がる感じかな。

キーワードからユーザーが何をしたいか想像できるページが良い

サイトの価値を高めることの分析だとキーワードが違えど魅力的なページではあれば上がるのでは?と疑問が出ると思うが、その通りで結局は魅力的であれば上がると思う。例えば「車」って検索すると「中古車」がトップに出てきて「車とは?」が出ない。車なんて誰でも知ってるからユーザーが何をしたいかと言うと「車を買いたい」とかなんだよね。まぁ、全く違うキーワードは流石に論外なんだろうけど。

生きてることが大事
サイトが生きてることも大事な要素だと思う。最近、ブログが下降ぎみ(だと思う)の理由は更新されないからだと分析。やはり「ユーザーがキーワードを用いて何かをしたい」を軸に考えると「今」が超重要で古くて価値のない情報は淘汰されるはず。ただ、その新しくて生きてる情報を分析するのはさすがのGoogleでもムズイと思うので更新頻度ってのが重要なんだろうと

歴史は超重要
結局はコレ。新しいサイトで上位に食い込むのはかなり難しいと思う。日本人の性質として新しい情報に抵抗があって、古い知ってるサイトを使いたくなるのは間違いないので最終的にアクセスされるのは古いサイト。海外のSEO事情とか知らないので適当なこと言ってるけどだいたい合ってそう。確実性を重要視する日本人の性質はめっちゃ良いと思うけどね!

個人サービスで戦うには

上の分析を踏まえながら戦っても個人での情報量、お金、運用が企業に絶対勝てないので以下の三つのどれかで戦うしかないだろうと思う

・ニッチなサービス
・ツール系
・犯罪系

最後のは論外だけど、勝てると言えるのはこの三つかなと。だけど諦める必要なんて全くなくて希望と運用の楽しさ、知識などはお金に換算出来ない価値なのでやってみるのは良いと思いました。