Photoruction工事中!

Photoructionの開発ブログです!

「一つのツールは、すべてを統べ」

開発者がよく作業上で使っている言語のツールチェインを変えたりはします。

最新版のプロジェクトなら、ほとんどの場合はツールチェインの最新版で実装されるだろう。

けれど、X年前からずっとサポートしないといけないプロジェクトもあります。

結局プロジェクトそれぞれ別のツールチェインに依存するのが珍しくありません。

おそれるな!バージョン管理ツールのお出ましだ!

幸いに、もうほとんどの言語のツールチェインがバージョン管理ツールをサポートしています。

はい、問題解決。

記事終わりです。皆さんありがとうございました。

と言いたいが、言語ごとバージョン管理ツールを使わなければなりません。

Pythonなら、pyenvです。NodeJSなら、nvmがメインです。などなど。

大きなシステムの開発者は大体言語一つでコードをすべて書くというわけではありません。

 

レガシーアプリケーションを考えるだけで難しいし、言語はそれぞれ、得意点、不得意点があります。フロントエンドコードなら、Javascript・Typescriptから逃げられません。

Webの一般的なバックエンドなら、PHPPythonRubyなどを使ってもおかしくないです。

 

パフォーマンスが必要なWebアプリケーションなら、Goも考えられます。

最速が必要な特殊な機能なら、C系又はRustが良いかもしれません。

結果として、各言語のバージョン管理ツールが必要になってしまいます。

NodeJS が好きな開発者の声を聞こえます。

 

はい、Nodeで全部かけるかもしれません。ただそれが一番いいですか?「出来る」からと言って、「良い」に限りません。(個人的な意見ですが・・・)



各ツールに特殊なコマンドと使い方を覚えないといけません。

正直、頭は別のことで使いたいです・・・

では、どうすればいいですか?

asdf と言うツールを紹介しましょう。プロジェクトホームはこちらです。

asdf はまず意外と覚えやすくて、打ちやすいコマンドです。

asdf を使って、言語ツールチェインに限らず、様々なツールやソフトのバージョン管理ができます。

一つのツールですべてを管理するのが絶好です。

ツール毎にプラグインがあります。そのプラグインをインストールすれば、簡単にバージョン管理ができます。

asdf のインストール

必要なツール(前提条件): curlgitMacの場合は Homebrew )

参考リンク

Mac & bash

brew install asdf
echo -e "\\n. $(brew --prefix asdf)/libexec/asdf.sh" >> ~/.bash_profile
echo -e "\\n. $(brew --prefix asdf)/etc/bash_completion.d/asdf.bash" >> ~/.bash_profile

Mac & zsh

brew install asdf
echo -e "\\n. $(brew --prefix asdf)/libexec/asdf.sh" >> ${ZDOTDIR:-~}/.zshrc

Linux / WSL2 & bash

git clone <https://github.com/asdf-vm/asdf.git> ~/.asdf --branch v0.10.2
echo -e "\\n. $HOME/.asdf/asdf.sh" >> ~/.bashrc
echo -e "\\n. $HOME/.asdf/completions/asdf.bash" >> ~/.bashrc

Linux / WSL2 & zsh

git clone <https://github.com/asdf-vm/asdf.git> ~/.asdf --branch v0.10.2
echo '. $HOME/.asdf/asdf.sh' >> ~/.zshrc
echo 'fpath=(${ASDF_DIR}/completions $fpath)' >> ~/.zshrc
echo 'autoload -Uz compinit && compinit' >> ~/.zshrc

ツールのプラグインをインストールする

どんなプラグインはデフォルトに入っているのかを分かるために、下記のコマンドを実行できます。

asdf plugin-list-all

仮にみんなの大好きな nodejs をインストールしたい場合は

asdf plugin add nodejs

plugin-list-all に含まれていないプラグインなら、追加パラメータのGitURLをプラグインの後に追加してください。

asdf plugin add hoge-matsuri <https://github.com/hogehoge/hoge-matsuri.git>

そして、NodeJS をインストールします。

# 特定のNodeJSをインストールする
asdf install nodejs <バージョン>

インストール出来るバージョンは

# インストール出来るNodeJSバージョンを取得
asdf list-all nodejs

で取得できます。

インストールした後、正式インストールできたかどうか

# インストール済みのNodeJSバージョンを取得
asdf list nodejs

これでインストールできたがまだ使用されていません!

バージョンアクテイブ化する

やり方は複数があります。目的によって、適切な方法を選択してください。

  1. 現在のシェルだけに使う

    asdf shell nodejs <バージョン>
    



    戻すために、シェルを閉じる、又は前のバージョンに戻すがいいです。

  2. 現在のフォルダの配下に使う

    asdf local nodejs <バージョン>
    

    これを実行すると .tool-versions ファイルが作成されます。

    このフォルダに入ると自動的に設定したバージョンが有効になります。



    注意: asdf shell.tool-versions のより優先されます。

    こちらはおすすめのやり方です。

  3. システム全体のデフォルトとして使う

    asdf global nodejs <バージョン>
    

    全体的にバージョンを設定します(ログイン中のユーザーのみ)。

追記①:特別な system バージョンがあります。使用中のバージョンを system にするとシステムにインストールされているバージョンになります。

追記②:例は nodejs で書いたが、他にサポートされているツールは色々あります。

  • terraform みたいなツールもありますし
  • sqlitepostgresql のようなDBも
  • 他のCLIツールもサポートされています: ripgreppeconeovim などなど

注意点

  • ツールチェインがバイナリーをインストールするとパスに入れられるためにたまに

    asdf reshim <プラグイン> 
    

    を実行する必要があります。

ツールのバージョン管理地獄から脱出できて何という幸せ!

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

Nuxt3 × Vuetify × Instagram APIで写真検索アプリ作ってみよう

こんにちは!株式会社フォトラクションでプロダクトマネージャーをしています南風原はえばる)です。

Photoruction Advent Calendar 2022の12日目の記事になります。

はじめに

ついに、待望のNuxt3が正式リリースされましたね👏

これからNuxt3を使ってみようと思っている初心者向けに、Nuxt3 × Vuetify × Instagram APIを用いた簡単な写真検索アプリをハンズオン形式で紹介していきたいと思います。

[前提]開発環境の各種バージョンは以下になります

os: macOS Monterey
node: v18.12.1
yarn: 1.22.11

Nuxt3をインストールしよう

npx nuxi init nuxt-instagram

cd nuxt-instagram

yarn install

yarn dev -o

Vuetify3をインストールしよう

yarn add vuetify@next mdi
yarn add -D sass

Nuxt3でVuetifyを利用できるようにしよう

// ./plugins/vueitfy.ts
import { createVuetify } from 'vuetify';
import * as components from 'vuetify/components';
import * as directives from 'vuetify/directives';

export default defineNuxtPlugin((nuxtApp) => {
  const vuetify = createVuetify({
    components,
    directives,
  });

  nuxtApp.vueApp.use(vuetify);
});
// ./nuxt.config.ts
export default defineNuxtConfig({
    css: ['vuetify/lib/styles/main.sass', 'mdi/css/materialdesignicons.min.css'],
    build: {
      transpile: ['vuetify'],
    },
})

Instagram Graph APIを利用できる準備をしよう

Instagramプロアカウントを作成

  1. Instagramアカウントを作成する
  2. 設定から「プロアカウントに切り替える」をクリックしプロアカウントに切り替える

Facebookページを作成

  1. Facebookにログインし、任意のFacebookページを作成する

    ※ページ名に「Instagram」など固有サービス名を含めるとページがBANされてAPI利用できないので注意!

  2. FacebookページとInstagramプロアカウントの紐づけをする

    1. 右上のアイコンクリック > 設定とプライバシー > 設定 > リンク済みのアカウント

    2. 「アカウントをリンク」よりFacebookページとInstagramアカウントを紐付けます

Facebookアプリを作成

  1. Facebook for Developersにアクセスし「利用を開始」するよりFacebook for Developersアカウントを作成する
  2. 「アプリを作成」> 「ビジネス」> 基本情報を入力する
  3. FacebookアプリにInstagram APIを追加する
    1. InstagramグラフAPI」を追加する
    2. 左サイドメニュー「設定」の中の「ベーシック」をクリック
    3. アプリIDとApp Secretキーをメモし、「変更を保存」をクリック

アクセストークンを取得

  1. 1番目のアクセストークンを取得する

    1. ヘッダーの「ツール」から「InstagramグラフAPIエクスプローラー」をクリック
    2. 作成したアプリを選択し、「ページアクセストークンを取得」をクリックし、作成したFacebookページを選択し「完了」をクリック
    3. ↓1番目のアクセストークンをコピーする

  2. 2番目のアクセストークンを取得する

// ブラウザで下記URLにアクセスする
https://graph.facebook.com/v15.0/oauth/access_token?grant_type=fb_exchange_token&client_id=${アプリID}&client_secret=${app secret}&fb_exchange_token=${1番目のトークン}
{
    "access_token": "2番目のトークン",
    "token_type": "bearer",
    "expires_in": 5183464
}
  1. 3番目のアクセストークンを取得する
// ブラウザで下記URLにアクセスする
https://graph.facebook.com/v15.0/me?access_token=${2番目のトークン}
{
    "name": "山田 太郎",
    "id": "12345678"
}

上で取得したIDを使って、ブラウザで下記URLにアクセスします。

https://graph.facebook.com/v15.0/${id}/accounts?access_token=${2番目のトークン}
// 3番目のアクセストークンが発行される
{
    "data": [
        {
            "access_token": "3番目のトークン",
            "category": "xxxx",
            "category_list": [
                {
                    "id": "123456",
                    "name": "xxxx"
                }
            ],
            "name": "Facebookページ名",
            "id": "FacebookページID"
        }
    ],
    "paging": {
        "cursors": {
            "before": "xxxx",
            "after": "xxxx"
        }
    }
}

InstagramビジネスアカウントIDの取得

// 上で取得したIDを使って、ブラウザで下記URLにアクセスする
https://graph.facebook.com/v15.0/${FacebookページID}?fields=id,name,instagram_business_account&access_token=${3番目のトークン}
// InstagramビジネスアカウントID取得結果
{
    "id": "FacebookページID",
    "name": "Facebookページ名",
    "instagram_business_account": {
        "id": "InstagramビジネスアカウントID"
    }
}

「3番目のアクセストークン」と「InstagramビジネスアカウントID」の取得ができたので、晴れてInstagram GraphAPIの利用が可能となります!

.envに環境変数を追加しよう

.envに環境変数を登録後、nuxtから呼び出せるようにnuxt.config.tsにも定義します。

// ./nuxt.config.ts
export default defineNuxtConfig({
    runtimeConfig: {
      public: {
        instagramApiUrl: process.env.INSTAGRAM_API_URL,
        userId: process.env.USER_ID,
        accessToken: process.env.ACCESS_TOKEN,
      }
    }
})

検索フォームを作成してみよう

// ./app.vue
<template>
  <SearchInput />
</template>
// ./components/SearchInput.vue
<script setup lang="ts">
interface State {
    loaded: boolean
    loading: boolean
    value: string
}

const state = reactive<State>({
    loaded: false,
    loading: false,
    value: ''
})

interface Emits {
  (e: 'search', value: string): void
}
const emit = defineEmits<Emits>()

const handleSearch = () => {
    state.loading = true
    setTimeout(() => {
        state.loading = false
        state.loaded = true
    }, 2000)
    emit('search', state.value)
}
</script>
<template>
    <v-text-field
        v-model="state.value"
        :loading="state.loading"
        density="compact"
        variant="solo"
        label="検索"
        append-inner-icon="mdi-magnify"
        single-line
        hide-details
        @click:append-inner="handleSearch"
     />
</template>

↓こういう感じで、表示されればOK👌

ハッシュタグ検索機能をつくろう

APIエンドポイントを定義

外部APIを直接呼び出そうとするとCORSエラーになってしまうので、server/apiAPIエンドポイントを定義して呼び出すようにします。

ハッシュタグのIDを取得

// server/api/hashtagId.ts
export default defineEventHandler ( async (event) => {
    const config = useRuntimeConfig()
    const query = getQuery(event)
    const result = await $fetch(`${config.public.instagramApiUrl}/ig_hashtag_search`, {
        params: query
    })
    return result
})

ハッシュタグの付いたメディアを取得

// server/api/recentMedia/[hashtagId].ts
export default defineEventHandler ( async (event) => {
    const config = useRuntimeConfig()
    const query = getQuery(event)
    const result = await $fetch(`${config.public.instagramApiUrl}/${event.context.params.hashtagId}/recent_media`, {
        params: query
    })
    return result
})

データ取得に関するロジックを書く

composablesディレクトリにデータ取得などのロジックを書くことで、Vue側で自動インポートされて利用できるようになります。

// ./composables/useInstagram.ts
import { Media } from '@/@types/instagram'

export const useInstagram = () => {
    const config = useRuntimeConfig()
    const medias = ref<Media[]>([]);

    const getHashtagId = async (hashtag: string) => {
        const response = await $fetch('/api/hashtagId', {
            params: {
                user_id: config.public.userId,
                access_token: config.public.accessToken,
                q: hashtag
            }
        })
        return response.data[0].id
    }

    const getRecentMedia = async (hashtag: string) => {
        const hashtagId = await getHashtagId(hashtag)
        const response = await $fetch(`/api/recentMedia/${hashtagId}`, {
            params: {
                user_id: config.public.userId,
                access_token: config.public.accessToken,
                fields: 'id,media_url,permalink,media_type,timestamp'
            }
        })
        medias.value = response.data.filter(i => {
            if(i.media_url && i.media_type === 'IMAGE') {
                return true
            }
            return false
        })

        return medias.value
    } 

    return {
        getHashtagId,
        getRecentMedia,
        medias
    }
}

Vue側でComposablesな関数を呼び出してみよう

<script setup lang="ts">
import { useInstagram } from '@/composables/useInstagram'
const { getRecentMedia, medias } = useInstagram()

const handleSearch = async (keyword: string) => {
  await getRecentMedia(keyword)
}
</script>

<template>
  <div class="wrapper">
    <SearchInput class="mt-8" @search="handleSearch" />
    <v-row class="my-8">
        <v-col
          v-for="media in medias"
          :key="media.id"
          class="d-flex child-flex p-2"
          cols="4"
        >
        <v-card min-width="100%">
          <v-img
            :src="media.media_url"
            aspect-ratio="1"
            cover
            class="align-end"
          >
            <template v-slot:placeholder>
              <v-row
                class="fill-height ma-0"
                align="center"
                justify="center"
              >
                <v-progress-circular
                  indeterminate
                  color="grey-lighten-5"
                ></v-progress-circular>
              </v-row>
            </template>
          </v-img>
        </v-card>
        </v-col>
    </v-row>
  </div>
</template>

<stype lang="scss" scoped>
  .wrapper {
    width: 65%;
    margin: 0 auto;
  }
</stype>

完成形

まとめ

実際作ってみて、Nuxt3を使う以前にInstagram Graph APIの事前準備のところで手こずってしまい意外と時間かかってしまいました。

久々に個人開発をして楽しかったので良しとします笑

Nuxt3になってからTypeScriptのサポートが強化されたのはもちろんのこと、ビルドのスピードがより速くなったり、各ディレクトリのルールに沿ってファイルを作成することで自動インポートをしてくれたり、書き方がシンプルになったり、などなどNuxt2よりも爆速で開発ができるようになった印象です。

まだまだ、情報が少ない部分もありますがぜひNuxt3を使っていきましょう〜♪

最後に、今回作成したものはGitHubにあげています。

少しでも参考になれば幸いです。

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

PyMuPDFでPDFを操作する①

はじめに

こんにちは、株式会社フォトラクションでエンジニアをしています酒井です。

自分のチームではPDFを扱うことが多く、これらを PyMuPDF と呼ばれるPythonのライブラリを用いて操作しています。

Pythonには他にも PyPDF2PDFminer など様々なPDF操作用のライブラリがありますが、日本語への対応や実装されている関数の多さ、ドキュメントの読みやすさ等をふまえ、弊社では PyMuPDFを使用しています。(参考: PyMuPDFドキュメント

今回はPyMuPDFで可能となる操作をいくつか紹介したいと思います。

1. 画像変換(pdf ⇒ ndarray)

import io

import cv2
import fitz
import numpy as np
from PIL import Image

# PDFファイルパスの読み込み
pdf_path = input()

# 1ページ目の要素を取得
document = fitz.open(pdf_path)
page = document[0]

# 拡大度を指定
zoom = 3.0

# pixmapを取得
matrix = fitz.Matrix(zoom, zoom)
pixmap = page.get_pixmap(matrix=matrix)

# pillowでpng形式のバイトオブジェクトを取得
byte = pixmap.pil_tobytes("png")

# PIL画像を取得
binary = io.BytesIO(byte)
pil_img = Image.open(binary)

# PIL画像をNumPy配列の画像に変換
ndarray_img = np.array(pil_img)

# BGR -> RGB
ndarray_img = cv2.cvtColor(ndarray_img, cv2.COLOR_BGR2RGB)

2. 注釈取得

# PDFファイルパスの読み込み
pdf_path = input()

# 1ページ目の要素を取得
document = fitz.open(pdf_path)
page = document[0]

# 注釈情報を取得
annotations = page.annots()

3. PDFサイズの取得

# PDFファイルパスの読み込み
pdf_path = input()

# 1ページ目の要素を取得
document = fitz.open(pdf_path)
page = document[0]

pdf_width, pdf_height = page.rect.width, page.rect.height

最後に

PyMuPDFを使えば、他にも「ページの分割」や「注釈の設定」等様々な操作が可能となります。

よかったら使ってみてください。

参考

Introduction - PyMuPDF 1.21.0 documentation

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

google driveで管理するファイルのIDを取得する方法

目次

  1. はじめに
  2. IDの取得方法
  3. 最後に

1. はじめに


時が過ぎるのは早いもので、AIエンジニアとしてキャリアをスタートしてから、早3年経とうとしています。

最近drive APIを使用して google driveのファイルを操作する機会があったのですが、その時に基本的にパスではなく、IDを使用して操作するような設計になっているようでしたので、ファイルの操作になかなか苦戦しました。

そこで、今回はパスからIDを取得するライブラリを発見したので、それを今回は紹介したいと思います。

2. IDの取得方法


手順として、まずkoraというライブラリをインストールします。

!pip install kora==0.9.20

そして、以下のように使用する事で、ファイルのパスからIDを取得する事ができます。

from kora.xattr import get_id
file_path = "./hogehoge/hogehoge.gsheet"
spreadsheet_id = get_id(file_path)

これで、ファイルのIDを取得する事ができました!

3. 最後に


これ以外にも、drive apiやsheets apiを使用する上で、例えばGCPのサービスアカウントに権限を渡す必要があるなど、ややこしい事が多々ありますが、IDの取得さえできれば他は結構簡単に解決していけると思うので、今回はdrive apiを使用する上で欠かせないIDの取得方法について解説しました。最後まで読んで頂きありがとうございました。

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

Androidアプリのアーキテクチャの現状 ver.2022年

Androidアプリ開発者の久木田です。

この記事はPhotoruction Advent Calendar2022の日目です。

去年のアドベントカレンダーAndroidアプリのアーキテクチャの現状 という記事を書きました。

今回はここから1年でどういう進捗があったのかをお話しします。

TL;DR

目を見張るほどのものはないかもしれないが、着実に良くなっていっている。

新しいことも始めていきたいな〜。。。

計測と所感

去年と同じ内容でデータを取ってみました。

色々改善が進んでいるのがわかるんじゃないないかなと思います。

ファイル数からわかること

種類 条件 コマンド
Activity 62 ファイル名にActivityがついている find . -name "*Activity.java" -o -name "*Activity.kt" | wc -l
Fragment 80 ファイル名にFragmentがついている find . -name "*Fragment.java" -o -name "*Fragment.kt" | wc -l
ViewModel 87 ファイル名にViewModelがついている find . -name "*ViewModel.java" -o -name "*ViewModel.kt" | wc -l
69 ↑のうち、ViewModelもしくはAndroidViewModelを継承しているクラスのあるファイル find . -name "*ViewModel.java" -o -name "*ViewModel.kt" | xargs grep -l AndroidViewModel | wc -l
Repository 82 ファイル名にRepositoryがついている find . -name "*Repository.java" -o -name "*Repository.kt" | wc -l
Realm 49 RealmObjectを継承しているクラスのあるファイル rg -l "extends RealmObject" . | wc -l
Room 31 @Entityが付いているクラスのあるファイル rg -l "@Entity" . | wc -l
Test 36 ファイル名にTestがついている find . -name "*Test.java" -o -name "*Test.kt" | wc -l

去年と比べての改善点

  • Activityがあまり変わらないけどFragmentが大幅に増えた → FragmentでViewが作られている
  • ViewModelの数が大幅に増えた
  • Repositoryが大幅に増えた

→ ViewModel、Repository経由でのDBアクセスへの書き換えが進んでいる

  • Realmは変わらないがRoomが増えている → 新規のテーブルがRoomで作られている

逆に、改悪した点は特に見受けられないかなと思います。

ステップ数からわかること

種類 条件 コマンド
平均 202 拡張子が.javaのファイルの行数の平均 wc -l /*.java | sort | sed '$d' | awk '{n += $1}{i +=1 }; END{print int(n/i) }'
80 拡張子が.ktのファイルの行数の平均 wc -l /*.kt | sort | sed '$d' | awk '{n += $1}{i +=1 }; END{print int(n/i) }'
中央値 90 拡張子が.javaのファイルの行数の中央値
35 拡張子が.ktのファイルの行数の中央値
長大ファイル 20 拡張子が.javaのファイルの1000行を超えているファイル数
0 拡張子が.ktのファイルの1000行を超えているファイル数

去年と比べての改善点

  • Javaファイルの行数が減少傾向
  • Kotlinは平均が少し上がっている

  • Kotlinで書いているViewModelでロジックが詰め込まれているファイルがあるためかなと予想している

  • 1000行越えのファイルが増えた

  • Realmのマイグレーションファイルが1000行を超えてしまったので+1になった

  • これをやってて気づいたのでこれから分割する予定

この1年での改善点

  1. packageの移動

  2. 設計方針で合意していた場所へほとんどのファイルを移動した。

  3. モジュール分割を始めた

  4. 修正による副作用の低減などを求めてモジュール分割をし始めた。
    まだまだやり始めで、3モジュールを切り出した程度ですが1歩前進といった感じ。

  5. 業務委託の方にガンガンリファクタリングをしてもらう体制になった

  6. 設計方針に沿ったMVVMの形にするために、釘宮さんをはじめ業務委託の方にリファクタリングをお願いすることにした。

  7. テストコードが大幅に増えた

  8. 新しいロジックに関しては基本的にテストを書いているのでどんどん増えています。

来年のやっていきたいこと

  1. リファクタリングの大幅な進捗

できれば、ほぼ全てのActivity/FragmentがMVVMな形になってるといいなと思いますがそれはちょっと夢を見過ぎかな…

  1. Crashlyticsの改善

現状が改善する価値があるくらいの数値なので、ここもテコ入れしていきたい

  1. 新しいことを始めたい

Jetpack ComposeやCameraXなど新しい技術の導入を進められればなと思っている

PS.

今回の記事を書くのにgrepの代わりにripgrepを使ってみましたが、すごい速くて快適だったのでおすすめです!

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

【初学者向け】はじめての公開鍵認証を用いたSSH接続

0. はじめに

フォトラクションでWEBエンジニアを担当している桑畑です。

初学者の方向けに、公開鍵認証を利用したSSH接続について記述していきます。既に理解されてる方には、新しい知見は無いかと思います。

こちらのテーマ選定をした背景は、私自身が曖昧な理解で何とな〜く利用していたため、、、理解向上のために調べたことや、AWSのEC2を利用して実験したことを記事にしております。SSH接続や公開鍵認証に対して少しでもイメージが掴んで頂ける情報になっていれば嬉しいです。

理解が間違っている箇所、説明に不備がある点などがあればご指摘頂けたらと思います。

1. SSH

1-1. SSHとは

SSHとは、リモートのサーバー・PCに対してセキュアに遠隔操作を可能にする通信プロトコルです。「サーバー → サーバー」や「ローカルPC → サーバー」などといった遠隔にあるサーバーに対して、SSH接続を用いることで安全にアクセスし、操作することが可能となります。

1-2. SSHの利用用途

SSH接続の利用用途は様々あるかと思います。

私がよく利用する機会は、ローカルPCからAWS-EC2インスタンスに対して接続する際に用います。SSH接続をしてEC2インスタンスのサーバー内を操作したり、scpコマンドでファイル転送をしたりしています。

1-3. SSHサーバー、SSHクライアント

SSH接続する側とSSH接続される側で、呼称が異なります。SSH接続をリクエストする側をSSHクライアントと言います。逆に、リクエストされる側をSSHサーバーと言います。ローカルPCからAWS-EC2インスタンスに対して接続する際には、ローカルPCがSSHクライアントであり、EC2インスタンスSSHサーバーです。

2. 公開鍵認証

2-1. 公開鍵認証とは

公開鍵認証とは、鍵(key)を利用して、通信の内容を秘匿にする仕組み・方法のことを指します。公開鍵認証でも幾つか手法があるようですが、一般的に用いられるのは鍵(key)は、公開鍵と秘密鍵の2種類のペアを用いることによって外部からの盗聴を防ぐ仕組みです。

2-2. 秘密鍵と公開鍵

秘密鍵と公開鍵とは何でしょうか。2つの鍵を理解するための概念として、情報を秘匿にして通信をするために『暗号化』と『復号化』という処理が存在します。文字通りではありますが、暗号化は、情報を外部から読み解け無い情報に変換します。 復号化は、暗号化された情報を複号して読み解ける状態にします。2種類の鍵を利用して外部から盗聴されることを防ぎます。

image_01

公開鍵 平文(暗号化されていない情報)を暗号化する際に用います。公開鍵は秘密鍵を利用して作成されます。通信対象であれば、公開鍵を複数人でも持つことが可能です。極端な話、公開鍵はばら撒いても問題にはなりません。 秘密鍵 暗号化された情報を平文へ複号する際に用います。秘密鍵は他人に公開するのはNGです。 暗号化の方式は複数存在するようです。「RSA」「DSA」「ECDSA」など、色々な方法があるようです。

3. 公開鍵認証を用いたSSH接続の実験 ~ 秘密鍵を発行していないサーバーへSSH接続 ~

最後に、EC2インスタンスで公開鍵認証を用いたSSH接続を試みます。

理解を深めるために、EC2インスタンスが発行していない秘密鍵を利用してSSH接続をします。

EC2-01、EC2-02という2つのインスタンスを用意し、それぞれのインスタンスから別々のキーペアを作成します。(別の秘密鍵をダウンロードします。)最終的には、EC2-01の秘密鍵を用いて、EC2-02にSSH接続をしてみます。※添付画像参照

3-1. EC2インスタンスが発行した秘密鍵を用いてSSH接続

まず、それぞれのEC2にSSH接続が可能かを確認します。 EC2-01が発行した秘密鍵-01を利用してSSH接続 (コマンドの解説は省略)

// コマンド例
ssh -i ~/.ssh/ec2_01.pem ec2-user@{EC2-01のIP}

次に、EC2-02が発行した秘密鍵-02を利用すれば、EC2-02へSSH接続は可能です。

// コマンド例
ssh -i ~/.ssh/ec2_02.pem ec2-user@{EC2-02のIP}

3-2. 他のEC2インスタンスが発行した秘密鍵を用いてSSH接続 (失敗を確認)

最終的に実行したいコマンドを試しに実行します。EC2-01が発行した秘密鍵-01を利用して、EC2-02へSSH接続

// コマンド例
ssh -i ~/.ssh/ec2_01.pem ec2-user@{EC2-02のIP}

現時点では、EC2-01が発行した秘密鍵-01を利用して、EC2-02へSSH接続すると接続不可となってしまいます。失敗を確認できました。

3-3. 他のEC2インスタンスが発行した秘密鍵を用いてSSH接続を成功させる

公開鍵認証を正しく実行するためには、秘密鍵-01が発行した公開鍵-01がSSHサーバー側に存在する必要があります。この仕組みを利用して、EC2-01の公開鍵01をEC2-02へ配置します。

公開鍵のファイルは、SSHサーバーの~/.ssh/authorized_keys に存在します。今回のSSH接続でいうと、EC2インスタンス~/.ssh/authorized_keys のファイルに記述されています。

下記のような文字列です。

// EC2−01インスタンス  ~/.ssh/authorized_keysファイル内
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCERdTPh2CG81cdU67g0sSxc8ghvbGH4Agjk63BsaPh+07X0u+xcXL5WwXxkcqdmacU7FCij+HOyrkzflEpjzILs4QHzcayQAUuHqIsB80X/IB416qyRNau3pBfwUrB3oeRY47rq3moeY6jiXKOoZt1qPhe4Jr3f+cd5qbtpCvkMc3XnPuJEtlqrvLxWWR2N3KCfXdSiV6SRPtTSrQaouybayWKXKOqXoXGKtn6gvdhwQYD+4dImYL0CDEWIlIyB6JS5XhtSwh8hScr77BdAp2lqECearIh+9xI1hx0pe5CDmnV9hI0ASUJSGtlXPM EC2-01

EC-01の公開鍵を、EC2-02の~/.ssh/authorized_keys に追加します。

// EC2−02インスタンス  ~/.ssh/authorized_keysファイル内
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCERdTPh2CG81cdU67g0sSxc8ghvbGH4Agjk63BsaPh+07X0u+xcXL5WwXxkcqdmacU7FCij+HOyrkzflEpjzILs4QHzcayQAUuHqIsB80X/IB416qyRNau3pBfwUrB3oeRY47rq3moeY6jiXKOoZt1qPhe4Jr3f+cd5qbtpCvkMc3XnPuJEtlqrvLxWWR2N3KCfXdSiV6SRPtTSrQaouybayWKXKOqXoXGKtn6htSwh8hScr77BdAp2lqECearIh+9xI1hx0pe5CDmnV9hI0A EC2-01
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD4gm2J7iXdRpHaIuLFenSNyC+Qc72ZJeNwsg8QKsrvFuo4tWiHWLH3hS199gKFq4QXlf8aaC3c3n6MD6i6hBm1xkHjfM1suSAQX7vL6QyW75G/D+uC/cQf8RRM/fClELMtY/CQqGqyaaFjHt1tMqJYOvzi3jl9jBGQ87PVEyiRnHdOX9jmyKTAW86FyRlhFY708ZTySry9c8DiPVbuNTllWVX+GoHDwI/heINoL97q7VlYCuYPHPE6+BIYxWqrAUwGqj/8IvcuLr8q1ZAaKh8 EC2-02

3-2. 他のEC2インスタンスが発行した秘密鍵を用いてSSH接続 (失敗を確認)で失敗したコマンドを再度実行します。

// コマンド例
ssh -i ~/.ssh/ec2_01.pem ec2-user@{EC2-02のIP}

これで、EC2-01が発行した秘密鍵を用いて、EC2-02へと公開鍵認証を利用したSSH接続が確認できました。

4. おわりに

如何でしょうか。説明を省略した部分や、理解の浅い部分もあったかと思いますが、SSH接続や公開鍵認証について少しでもイメージを湧いて頂けたのであれば嬉しく思います。

AWS APIgateway + Lambda を環境ごとに切り分けるTips

こんにちは!株式会社フォトラクションでWEB開発をしている下川原です。

Photoruction Advent Calendar 2022の7日目の記事になります。

はじめに

どんな人がこの記事を読むと幸せになるか。

AWS APIgateway + Lambdaを環境ごとに分ける!

1. 下準備

API Gatewayを既に作成済みで、1つ以上のメソッドがあることが重要!

検証用、本番用のLambda関数ができていることが重要!

AWSLambdaを環境ごとにわける方法は、前回の記事を参考に

AWS Lambdaで効率よく本番・検証環境をわける!

2. AWSLambdaをエイリアスで呼び出す

Lastateを使用する
arn:aws:lambda:ap-northeast-1:000000000:function:myTestsFunctions

エイリアスstagingを使用する
arn:aws:lambda:ap-northeast-1:000000000:function:myTestsFunctions:staging

Lambdaを呼び出す際に末尾に:{alias}をつけることで、エイリアスを呼び出すことが可能である

そのため、APIGatewayに設定Lambda名を工夫する必要がある

統合リクエストから、Lambda関数末尾に以下のように追加する

関数名:${stageVariables.alias}

追加を行うと、エイリアス関数に権限を追加するコマンドが表示されるので、AWS CLIでコピーして実行する

コピーしてそのまま実行はできないので注意

${stageVariables.alias}

かならずエイリアス関数名に置換してから実行すること ※すでに存在するエイリアス関数すべて実行しておくと良い

3. ステージを作成する

リソースのアクションからAPIのデプロイを選択すると、新しいステージを作ることができる

  • デプロイされるステージ
    • [新しいステージ]を選択
  • ステージ名
    • 検証用や本番用のものとわかりやすいのが望ましい
  • ステージの説明
    • 用途を記載
  • デプロイメントの説明

    • デプロイ時の更新内容などあるとわかりやすい
  • ステージ変数を設定する

    ステージ変数を設定することで、Lambda関数を呼び出す際にエイリアスで実行することができる。

  • 名前
    • 今回は、手順.2で${stageVariables.alias}を設定しているので、alias を入力
  • APIを更新するとき

    APIを更新したいときは、手順3のようにアクションからAPIのデプロイを行うことで

    作成したステージにAPI更新を反映することができる

6.APIGatewayステージを利用した運用方法

上記までの手順で、APIGatewayをステージごとにわけることができた。

実際に運用する際は、カスタムドメインから開発用、検証用、本番用のドメインを選択し

カスタムドメインAPIマッピングで、作成したそれぞれのAPIGatewayステージを紐づけることができる

参考:REST API の API マッピングの使用

さいごに

APIGatewayで、環境をわけてLambdaを使うことで

柔軟なAPI開発を行うことができる。

ぜひ、煩雑な環境管理から開放されて、

開発に集中できるよう活用してほしい

参考

AWS Lambda関数のエイリアス使用

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