Kubernetesを学習するにあたって出てきた用語をまとめてみる

目的

  • 個人でKubernetes(以下k8s)を学習したときに関連用語がたくさん出てきて今現在ほとんど頭から抜けているため復習がてら用語をブログにまとめてみる

概要

  • ここでは、k8sについての概要説明等は、いたしません。以下をご参考ください。

kubernetes.io

k8sを構成する要素の概要と言葉

kubectl

  • K8s クラスタを操作するためのコマンド
  • Macならbrew install kubectlで入ります

コントロールプレーンコンポーネント

kube-apiserver

  • API サーバーは kubectl などの API クライアントからの REST リクエストを検証して API オブジェクトを構成、または、状態を報告する

kube-scheduler

  • ワークロード専用のスケジュール機能である

Kubernetesのスケジューラー | Kubernetes

kube-controller-manager

  • 制御ループを使ってシステム状態を調整する。モニタリングした現在状態から希望状態への遷移を実行する

cloud-controller-manager

Kubernetesのコンポーネント | Kubernetes

etcd

  • K8s クラスタのすべての管理データは etcd で保存される。

kubelet

  • 各ノードで動作する。
  • ボッッドとコンテナの実行
  • ポッドとノードの状態を API サーバーへ報告する
  • コンテナを検査するブローブを実行
  • 内臓する cAdvisor がメトリックスを集約して公開する
  • k8sが作成したものではないコンテナは管理しません。

kube-proxy

  • 各ノードで動作し、高可用性かつ低オーバーヘッドのロードバランシングを提供
  • サービスとポッドの変更を API サーバーで監視し、構成を最新状態に保ち、ポッド間とノード間の通信を確実にする
  • サービスの生成時に ClusterIP へのパケットをトラップして、対応するポッドへリダイレクトするように、iptables のルールを操作する
  • サービス名と ClusterIP をアドオンの DNS へ登録する

coredns

  • ポッドがサービス名から IP アドレスを得るために利用されている

kube-flannel

  • すべてのノードで実行され、複数のノードの間で IPv4 ネットワークを提供する。これによりコンテナ(ノード)は K8s 内部の IP アドレスでノードを超えて、疎通できるようになる
  • ネットワークポリシーを必要とする場合には、calico を使用しなければならない

calico-kube-controllers

  • calico のためのコントローラ。データストアとしての etcd を利用するために使われる

calico-node

  • 全てのノードで実行され、ノード間のコンテナ(ポッド)の疎通、アクセスコントロール、ルーティングを提供

kubernetes-dashboard

  • WebUI

metrics-server

  • heapster に代わり API の aggregation layer を通じて、K8s クラスタ全体のメトリクスを収集する

k8sを触る際によく出る言葉や概要

コンテナ

  • 必ずポッド内で実行する
  • 軌道に設定できる項目がある

ポッド

  • コンテナを実行するためのオブジェクトで、複数のコンテナを内包している

コントローラ

  • ポッドの実行を制御するオブジェクト

コンフィグレーション

  • コンテナ内のアプリケーションの設定やパスワードなどの情報は、デプロイされた「名前空間」から取得することが推奨されている。
  • 設定を保存する:ConfigMap(コンフィグマップ)
  • 秘匿情報を保存する:Secret(シークレット)
  • 名前空間下に保存された情報は、コンテナ内のファイルや環境変数として、アプリケーションのコードから参照できるようになる

k8sの状態を示す言葉と概要

ContainerCreating

  • イメージをダウンロード中、またはコンテナ起動進行中を表す。
  • ConfigMap や Secret をマウントできず、コンテナ生成が保留された時もこの値が表示される

CrashLoopBackOff

  • ボッド内のコンテナが終了し、次の起動まで待機状態になる。
  • コンテナ内のプロセスを見直す必要がある

Pending

  • ポッド生成の要求を受け取るが、1 つ以上のコンテナが作成されていない状態
  • リソース不足、ポリシー制約によってスケジュールできていないケースがあるため見直す

Running

  • ノードに対応づけられ、少なくとも 1 つのコンテナが実行中、開始中、または再起動中である

Terminating

  • コンテナへの終了要求シグナルが送られ、コンテナが終了するまで待機中
  • 猶予時間をすぎ、コンテナが終了できていない場合、強制終了する

Succeeded

  • 正常

Completed

  • ポッド内のコンテナが正常終了し存在している。削除されるまで存在し続ける。ポッド名を指定することでログやステータスを取得できる
  • ポッド内に複数のコンテナがある場合、第 1 コンテナが正常終了するとポッドは、正常終了として扱われる

Error

  • コンテナが異常終了した。
  • Completed の対義

Failed

  • ポッドないの少なくとも 1 つのコンテナが異常終了した

Unknown

  • 何らかの理由により、ポッドの状態を取得できない状態
  • また、ノード障害、マスターからノードの状態を取得できなくなったときにもこの表示になる

参考資料

kubernetes.io

SQLアンチパターン~第4章~

趣旨

  • 最近学習をサボっておりアウトプットも行っていない。エンジニアとして後退する老害になりかねない。こんなんじゃだめだ!という理由で書きます。
  • 最近「ん?ちょっと何言っているわからない」ってことが本業外であり、会話を成立させるには、体系的に理解しているのでなくちゃんと言語化できるレベルで理解しようねってことでまとめます。

おおまかな流れ

  • みんな大好き「SQLアンチパターン」の第4章キーレスエントリー(外部キー嫌い)を再読とまとめ
    • 物量的に全然多くなく主張も簡潔です。
  • なぜ第4章なのかの話
  • Laravel で外部キー制約を設定する
  • まとめ

第4章の主張

  • パフォーマンスが改善し、単純複雑を問わず、データの変更における参照整合性の意地に役立つよってデーターベースでのミスを未然に防ぐために外部キー制約を用いましょう。

主張に関して

  • 基本的に例外を除いて外部キー制約を使わない理由は、あまりないですよって話。

アンチパターン

  • そもそも外部キー制約を適用できない
  • DB の設計を柔軟にしなければならない

アンチパターンの見つけ方

  • A テーブルには存在するけど、B のテーブには存在しない値を調べるクエリはどう書く?
  • A テーブルの値が、別のテーブルへの挿入に使われている子を簡単にチェックする方法はある?
  • 外部キーは、DB の実行速度を遅くするからつかってはならない
    • 外部キー制約を設けることでの起きる問題より設けない時の問題の方が障害になる可能性が高い  

以上の発言があるとアンチパターンの兆候がある

なぜ第4章の話なのか

  • データ構造を追いかける際に(ER図吐き出したり)外部キーがあると大変便利でだよね。(だから使ってくださいと言いたい)

  • なので職場に外部キー設定しません!っていう人がいたら第4章をお経のように読み上げ続けましょう。個人的に外部キーがないデータの全体構造を理解するのは、できるとはおもいますが時間の浪費になるので勿体ないことになるんだろうな〜っておもっています。仕事上時間の浪費は、本当に無駄です。

Laravelで外部キー制約を設定する

  • 前提:MySQL, MariaDB

  • migration fileを作成し、fileを編集します。

php artisan make:migration CreatePosts
php artisan make:migration CreateComments
  • 今回は、外部キーを設定するのが目的なので最小限のカラムを書きます(型は気にせず)
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePosts extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('user_id')->comment('user_idを格納します');
            $table->text('content')->comment('投稿内容を保存します');
            $table->timestamps();

            $table->foreign('user_id')
                ->references('id')
                ->on('users');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('posts');
    }
}
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateComments extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('comments', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('post_id')->comment('posts tableのidを格納します。');
            $table->text('comment')->comment('コメントを格納します');
            $table->timestamps();

            $table->foreign('post_id')
                ->references('id')
                ->on('posts');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('comments');
    }
}
php artisan migrate
  • 注意点としては、外部キーを設定する対象の型を合わせないとならない点です
    • 最新のLaravelだとmigration fileを作成した際にデフォルトで記載ある、auto incrementsのカラムがidとなった点なりました。こちらの型(Laravel上で使用するメソッド)は、unsignedBigIntegerとなるので注意してください。
    • また外部キー制約を設定する際にindexも作成してくれているんですが、table名とid名が長いとエラーになりますので、foreign()の第2引数にindex名を指定することで回避が可能です。
    • 個別(create時ではなくaddなどで)に外部キーを設定した際は、必ずdownを記述しましょう。エラーでmigrateがエラーになります。
  • 外部キーの設定を行う際に、onDelete()という親tableへの動作に応じて振る舞いを連動させるメソッドがあるんですが、今回は、設定しません。

  • ちなみに外部キー設定などに関する情報は、information_schemaというDBに保存されます。

まとめ

  • 外部キー制約は、ちゃんと設定しましょう
  • Laravelで普通に設定する分には、なんら問題なく動作することがわかりました。しっかりFWの使える機能は、利用しましょう

続) GitHub Actionsの何か

はじめに

昨年末に実家が空き巣に入られたり、年始早々身体に異変があってメンタル的に疲れているところに自分史上間違えなく上位に入る節々の痛さと突発的高熱、その翌週には、首を盛大に寝違えてキョンシーみたいな姿勢での生活を余儀なくされました。どうも僕です。

ちなみに巷で流行っているコロナではなく、ただの風邪で2日半くらいで回復して味覚等々を失うことなく今は、元気に業務と麻雀に励んでいます。

前回のブログ

  • GitHub Actionsのcronがあるらしいってことでそれを使ってアニメの放送日にSlackへ通知しようぜ!ってのをやりました。

あれから一ヶ月

  • おかげさまでアニメを見逃す日がなくなりました!(素晴らしい)

改善点と改善案

GitHub Actionsのcronラグあり過ぎじゃね?問題

  • こちらは、無料枠で無料でGiHub様の善意の元リソースを使わせていただいているのでちょっと文句言いたいけどスルーすることにしました
    • ただ何かしらのプロダクトには、活用場面なさそうって思いました。

Jsonデータで管理するのだるい問題

  • 前回の目的は、見たいアニメの放送を見逃さない。っていう点にしか焦点は当たっておらずデータの収集や管理に関しては、全くの無関心でした
  • なので改善点としては、以下になると思います。
    • JsonデータのGitHub以外での管理
    • Jsonデータを自動で作成する

改善点挙げておいてなんですが何かと初物や久々(何かを)に触るのがいいと思っていますので、遠い昔に触ったFirebaseでJsonを管理しようかなと考えたんですがクレデンシャルをどう管理すんねんS3か?って考えたんですがこのプロジェクトは、すべてを無料で!を心がけているので却下です。(微々たる金額で済むのも想定できるんですがね)

今回の実装

スクレイピングJsonデータ作るぞの巻きに決定しました。

やらないこと

前提

import requests
from bs4 import BeautifulSoup
from os import getenv
import json
import datetime as dt
import calendar

today = dt.date.today()
dayName = calendar.day_name[today.weekday()]

if today.month < 10:
    month = f'0{today.month}'
else:
    month = today.month

weekList = {
    '月': 'Monday',
    '火': 'Tuesday',
    '水': 'Wednesday',
    '木': 'Thursday',
    '金': 'Friday',
    '土': 'Saturday',
    '日': 'Sunday'
}

load_url = getenv('SCRAPING_TARGET_URL')
html = requests.get(load_url)
soup = BeautifulSoup(html.content, "html.parser")

animeList = soup.find(class_='week_area').find_all('li')

jsonList = []
for e in soup.find(class_='week_area').find_all('li'):
    week = weekList[e.find(class_="oatime").find(class_='youbi').text]
    e.find(class_="oatime").find(class_='youbi').decompose()
    e.find('h4').find('strong').decompose()

    item = {
        'title': e.find('h4').text,
        'publish_at': e.find(class_="oatime").text,
        'channel': 'TOKYO MX',
        'day_of_week': week
    }
    jsonList.append(item)


with open(f'./data/anime/{today.year}{month}.json', 'w') as f:
    json.dump(jsonList, f, ensure_ascii=False, indent=4)

前回同様解説していきます

コードを読めばわかるって内容なので解説すべきことはないんですけどね!   スクレイピングしたあとのDOM操作に関しては、ほんと愚直な方法しか思いつかなかったので誰かいい方法があったら教えて欲しいです

  - あーどうしよっておもったDOM操作が下記のようなHTMLでした

<div class="oatime"><p class="youbi"></p>25:05~ </div>
  • こんな形のHTMLだとclass="oatime"直下のテキストを抜くと25:05〜が改行付きで取得できてしまう。なので先に曜日だけ取得して曜日のクラスのDOMを削除して時刻の取得を行うってことをしました。
    • 下記のような処理
week = weekList[e.find(class_="oatime").find(class_='youbi').text]
e.find(class_="oatime").find(class_='youbi').decompose()

さきに最終的なJsonファイルをどうぞ

[
    {
        "title": "\n7SEEDS\n",
        "publish_at": "22:30~ ",
        "channel": "TOKYO MX",
        "day_of_week": "Monday"
    },
    {
        "title": "\nたとえばラストダンジョン前の村の少年が序盤の街で暮らすような物語\n",
        "publish_at": "23:00~ ",
        "channel": "TOKYO MX",
        "day_of_week": "Monday"
    },
   ......
]
  • 最終的に前回手動で作成したJsonファイルと同じような形式をとることにしました。(処理をわざわざ変えるのもなーっておもったので)
    • ファイル名に関しては、前回ファイル名を取得する際の方法で生成したyyyymmの形式にしました。

新たな問題点

  • TOKYO MXしか見ねー的なノリで始めたんですが、推しキャラなの二乃がいる五等分の花嫁約束のネバーランドがT◯Sやフ◯TVじゃないですか。
  • これに関しては、それ以外の取得に関して考えて、結果マージしたデータでSlackに飛ばすようにするんだろうなってうっすら考えてます。まぁどちらもアマプラでいつでも見れるんですけどね!

  • いつcronで動かすよ問題!

    • 日次で動かす内容ではないし、月1でも違うし、1クール単位がいいんですがー言うてそんな設定できるんか?基準日を考慮できないと思うのでn日毎には実行難しそう。
    • ってことでこちらは、おそらくPython側で判定する形にするでしょう!月1実行とかにして。

反省

Pythonのお作法わからずだぁーって書いてしまっているので、リファクタリングとmodule化等を行っていきたいと思います。年末年始、特に年末が何してたかわからんくらいな状態だったので何もできなかったのでこれから平日含め学習をする癖がつくことただただ願います。

最後に独り言

アニメの通知ってamebaTVでできません?やアルっていう漫画サービスで発売日通知できますよって会社の同僚に言われました。「そそそそそうだったの」って内心では、思ったのは、内緒です。

GitHub ActionsにCronがあると聞いたんだ

はじめに

腹筋ローラーを初めて以降、筋肉痛なのか、姿勢の悪さからのコリなのかわからない苦しみが起きています。

あとウォーキングデットやゴールデンカムイを見ていついかなる状況になっても生存しなくてはいけないと感じてキャンプに興味を持ちました。

さて今回の目的

GitHub ActionsのCronで何すっかって話なのですが

最近に限ったことではなくて、僕は、数字の4桁以上を覚えるのがなかなか苦手なレベルの脳内メモリ所有者なのですが

来期のアニメの見なくてはいけないタイトルが10タイトルくらいあり、放送日時を把握し切れる訳がないじゃないか!って危機的状況に陥っている訳です。

仮にリアタイで見れなくてもアマプラさんで解決する部分もあったり絶命するわけではないのですが、個人的にHPが2割以下くらいになって日々のモチベーションがダダ下がりするのだろうと想定されるのです。

なのでよしGitHub Actionsの学習がてら毎朝か業後くらいの時間に通知行くようにしようぜ!

なんならPythonでやってみようぜ!

って昨日の夜中に思い立ったのでやってみたいとおもいます。

補足

我が家は、録画機能を有した何かしらの文明機器を持ち合わせていません。

前提

ゴール

最終的に以下のようにslackへ放送日に通知が行くようにします。 f:id:hironekosun:20201223154138p:plain

実装:Python

コードを先に晒します。

import json
import slackweb
from os import getenv
import datetime as dt
import calendar

today = dt.date.today()
dayName = calendar.day_name[today.weekday()]

if today.month < 10:
    month = f'0{today.month}'
else:
    month = today.month

data = json.load(open(f'./data/anime/{today.year}{month}.json', 'r'))

slack = slackweb.Slack(url=getenv('SLACK_WEBHOOK_URL'))

for v in data:
    if v['day_of_week'] == dayName:
        attachments = [
           {
               "fallback": 'アニメの放送時間のご案内',
               "pretext": '本日放送のアニメ',
               "fields": [
                   {
                       "title": 'タイトル',
                       "value": v['title'],
                   },
                   {
                       "title": '放送時間',
                       "value": v['publish_at'],
                       "short": "true"
                   },
                   {
                       "title": 'チャンネル',
                       "value": v['channel'],
                       "short": "true"
                   }
               ]
           } 
        ]
        slack.notify(attachments=attachments)

解説

  • 特別語ることは少ないです。

  • slackweb

こちらのライブラリが一番手っ取り早く簡単そうだったので使いました。

所感としては、ほんと単純なコードで通知することが可能なので誰でも迷わず実装が行えると思います。

  • getenv

getenvを使用して環境変数を取得してslackのweb hook urlをコードにハードコードしないようにします。

  • 全体

読めばわかるとは思うのですが、予めJSONのfileにデータを入れておきそのデータを元に今日放送をするアニメかどうかを判定して通知を行っています。

.
├── attachments.json
├── data
│   └── anime
│      ├── 202001.json
│      └── 202012.json
├── read.py
└── requirements.txt
  • JSON 下記の形式で通知対象にしたい情報を入れておきます(ここはスクレイピングでどうにかしたい)

file名は、必ずyyyymm.jsonとします。

[
    {
        "title": "転生したらスライムだった件",
        "publish_at": "23:00",
        "channel": "TOKYO MX",
        "day_of_week": "Tuesday"
    },
    ....
]

実装:GitHub Actions

  • こちらも先にコードを晒します。
on: 
  schedule:
    - cron: '55 0 * * *'

jobs:
  slack:
    name: Run slack
    runs-on: ubuntu-latest
    env:
      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Set up Python 3.8
        uses: actions/setup-python@v1
        with:
          python-version: 3.8
      - name: pip install
        run: pip install -r requirements.txt
      - name: post
        run: python read.py
  • cron

注意点として5分以下は動かないっぽいのとdefault branch以外は、cronの対象にならないとのこと

またこのcronが結構いい加減なので5分くらいのズレは、デフォです。

なので今回実行時間を10時に実行されるといいなーって気持ちでUTC 00:55 = 日本時刻 09:55 に設定しました。

GitHub Actionsよろしく!

  • secrets

今回slackのweb hook urlを使いますのでコードにハードコードしたくなく、外に晒すのもなーっていう気持ちなのでGitHubの機能を使用しました

Settings > Secretsで設定ページへ遷移します。New repository secretボタンを押下し追加します。

使い方は${{ secrets.KEY_NAME }}となります。

今後

漫画やアニメが好きなんですが既存の世に出ているサービスでは、僕のやってほしいことを満たすサービスが1mmもないのでこれを気に作ってやるんだから!って気持ちがふつふつと湧いてきました。

年末年始、時間があるようでないようなものですが積読している書籍読みつつ、開発していきたいなって気持ちです。

最後に独り言

グーカレでよくね?という意見は、受け付けますん。すみませんブログネタ考えたあとに振り返ったらグーカレの定期でよくねって考えに至りました。

Next.js + TS + Tailwindで入門してみる

はじめに

最近腹筋ローラーを買って速攻で筋肉痛になりました。The walking deadもやっとシーズン10まで見終わりそうです。

なぜNext.jsを触るのか?

在籍している会社にてNext.jsを使用する流れになり、入門はしたけども忘れてしまったから再度入門して肩慣らしでもしておこう!という気持ちになり、今回触ることにしました。 TSにしているのは、会社ではTSで実装しているため多少いじるにしてもTSの方がいいでしょう!という理由です。

Next.js

nextjs.org

Nuxt.jsより以前から開発が行われているReactのフレームワークです。

公式に物凄い数のexampleが用意されています。

github.com

ゴール

今回は、Next.jsを開発しているVercel社が用意しているサンプルにTailwindを適用してみるの巻です。

参照

kakakakakku.hatenablog.com

今回は、参照記事を元に公式が用意しているexampleのwith-typescriptを使用してみたいとおもいます。

準備

  • nodeのversionは、v14.15.1を使用します。
mkdir next-ts-sample
cd next-ts-sample
npx create-next-app --example with-typescript .
  • installが終わったのでyarn devを実行してみる。下記画像のような表示がされました。

f:id:hironekosun:20201209140713p:plain

  • Next.jsのアプリ全ページにTailwindを使えるようにするためにpages以下に下記fileを作成します。
    • ComponentなどをLayoutなどでラップしてあげるといいです
import './styles/tailwind.css'

function App({ Component, pageProps }) {
    return <Component {...pageProps} />
}

export default App

Tailwind入れる

yarn add -D tailwindcss@latest postcss@latest autoprefixer@latest
npx tailwindcss init --full
touch pages/styles/tailwind.css
  • 作成したtailwind.cssに以下を追加します
@tailwind base;
@tailwind components;
@tailwind utilities;

tailwind.config.jsに追記

  • Purge オプションを使ってビルドサイズの縮小するこができます。
const colors = require('tailwindcss/colors')

module.exports = {
  purge: ['./components/**/*.jsx', './pages/**/*.jsx']

// 省略

postcss.config.jsの作成

module.exports = {
  plugins: ['tailwindcss', 'autoprefixer'],
}

ブラウザで一度確認してみる

  • さきほどまでと様子が違うのでtailwindがあたってそうですね。

f:id:hironekosun:20201209154509p:plain

Tailwindを感じてみよう!

  • pages以下にweather.tsxを作成し以下を記述する
const Weather = () => (
    <div className="min-h-screen flex items-center justify-center">
        <div className="border-solid border-2 flex flex-col bg-white rounded p-4 w-full max-w-xs">
            <div className="font-bold text-xl">Sydney</div>
            <div className="text-sm text-gray-500">Thursday 10 May 2020</div>
            <div className="mt-6 text-6xl self-center inline-flex items-center justify-center rounded-lg text-indigo-400 h-24 w-24">
                <svg className="w-32 h-32" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z"></path></svg>
            </div>
            <div className="flex flex-row items-center justify-center mt-6">
                <div className="font-medium text-6xl">24°</div>
                <div className="flex flex-col items-center ml-6">
                    <div>Cloudy</div>
                    <div className="mt-1">
                        <span className="text-sm"><i className="far fa-long-arrow-up"></i></span>
                        <span className="text-sm font-light text-gray-500">28°C</span>
                    </div>
                    <div>
                        <span className="text-sm"><i className="far fa-long-arrow-down"></i></span>
                        <span className="text-sm font-light text-gray-500">20°C</span>
                    </div>
                </div>
            </div>
            <div className="flex flex-row justify-between mt-6">
                <div className="flex flex-col items-center">
                    <div className="font-medium text-sm">Wind</div>
                    <div className="text-sm text-gray-500">9k/h</div>
                </div>
                <div className="flex flex-col items-center">
                    <div className="font-medium text-sm">Humidity</div>
                    <div className="text-sm text-gray-500">68%</div>
                </div>
                <div className="flex flex-col items-center">
                    <div className="font-medium text-sm">Visibility</div>
                    <div className="text-sm text-gray-500">10km</div>
                </div>
            </div>
        </div>
    </div>
)

export default Weather
  • localhost:3000/weatherにアクセスする f:id:hironekosun:20201209161131p:plain

  • pages以下にcard.tsxを作成し以下を記述する

const Card = () => (
    <section className="flex flex-row flex-wrap mx-auto">
        <div
            className="transition-all duration-150 flex w-full px-4 py-6 md:w-1/2 lg:w-1/3"
        >
            <div
                className="flex flex-col items-stretch min-h-full pb-4 mb-6 transition-all duration-150 bg-white rounded-lg shadow-lg hover:shadow-2xl"
            >
                <div className="md:flex-shrink-0">
                    <img
                        src="https://www.unfe.org/wp-content/uploads/2019/04/SM-placeholder-1024x512.png"
                        alt="Blog Cover"
                        className="object-fill w-full rounded-lg rounded-b-none md:h-56"
                    />
                </div>
                <div className="flex items-center justify-between px-4 py-2 overflow-hidden">
                    <span className="text-xs font-medium text-blue-600 uppercase">
                        Web Programming
                    </span>
                    <div className="flex flex-row items-center">
                        <div
                            className="text-xs font-medium text-gray-500 flex flex-row items-center mr-2"
                        >
                            <svg
                                className="w-4 h-4 mr-1"
                                fill="none"
                                stroke="currentColor"
                                viewBox="0 0 24 24"
                                xmlns="http://www.w3.org/2000/svg"
                            >
                                <path
                                    stroke-linecap="round"
                                    stroke-linejoin="round"
                                    stroke-width="2"
                                    d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
                                ></path>
                                <path
                                    stroke-linecap="round"
                                    stroke-linejoin="round"
                                    stroke-width="2"
                                    d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
                                ></path>
                            </svg>
                            <span>1.5k</span>
                        </div>

                        <div
                            className="text-xs font-medium text-gray-500 flex flex-row items-center mr-2"
                        >
                            <svg
                                className="w-4 h-4 mr-1"
                                fill="none"
                                stroke="currentColor"
                                viewBox="0 0 24 24"
                                xmlns="http://www.w3.org/2000/svg"
                            >
                                <path
                                    stroke-linecap="round"
                                    stroke-linejoin="round"
                                    stroke-width="2"
                                    d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z"
                                ></path>
                            </svg>
                            <span>25</span>
                        </div>

                        <div
                            className="text-xs font-medium text-gray-500 flex flex-row items-center"
                        >
                            <svg
                                className="w-4 h-4 mr-1"
                                fill="none"
                                stroke="currentColor"
                                viewBox="0 0 24 24"
                                xmlns="http://www.w3.org/2000/svg"
                            >
                                <path
                                    stroke-linecap="round"
                                    stroke-linejoin="round"
                                    stroke-width="2"
                                    d="M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.017c-.163 0-.326-.02-.485-.06L7 20m7-10V5a2 2 0 00-2-2h-.095c-.5 0-.905.405-.905.905 0 .714-.211 1.412-.608 2.006L7 11v9m7-10h-2M7 20H5a2 2 0 01-2-2v-6a2 2 0 012-2h2.5"
                                ></path>
                            </svg>
                            <span>7</span>
                        </div>
                    </div>
                </div>
                <hr className="border-gray-300" />
                <div className="flex flex-wrap items-center flex-1 px-4 py-1 text-center mx-auto">
                    <a href="#" className="hover:underline">
                        <h2 className="text-2xl font-bold tracking-normal text-gray-800">
                            Ho to Yawn in 7 Days
                        </h2>
                    </a>
                </div>
                <hr className="border-gray-300" />
                <p
                    className="flex flex-row flex-wrap w-full px-4 py-2 overflow-hidden text-sm text-justify text-gray-700"
                >
                    Lorem ipsum dolor sit amet consectetur adipisicing elit. Alias, magni
                    fugiat, odit incidunt necessitatibus aut nesciunt exercitationem aliquam
                    id voluptatibus quisquam maiores officia sit amet accusantium aliquid
                    quo obcaecati quasi.
                </p>
                <hr className="border-gray-300" />
                <section className="px-4 py-2 mt-2">
                    <div className="flex items-center justify-between">
                        <div className="flex items-center flex-1">
                            <img
                                className="object-cover h-10 rounded-full"
                                src="https://thumbs.dreamstime.com/b/default-avatar-photo-placeholder-profile-icon-eps-file-easy-to-edit-default-avatar-photo-placeholder-profile-icon-124557887.jpg"
                                alt="Avatar"
                            />
                            <div className="flex flex-col mx-2">
                                <a href="" className="font-semibold text-gray-700 hover:underline">
                                    Fajrian Aidil Pratama
                                </a>
                                <span className="mx-1 text-xs text-gray-600">28 Sep 2020</span>
                            </div>
                        </div>
                        <p className="mt-1 text-xs text-gray-600">9 minutes read</p>
                    </div>
                </section>
            </div>
        </div>
    </section>
)

export default Card
  • localhost:3000/cardにアクセスする f:id:hironekosun:20201209161203p:plain

感想

今回のゴールがあくまでTailwindの適用なのでTSである必要なかったな

フロントのキャッチアップは、目に見えてわかるから楽しい反面、エラーが発生したりすると何を言っているんだって気持ちになるんで億劫になりがちだけど

今後も入門程度でもいいからいろいろ触って経験値だけは積んでいきたいとおもった。

あとVSCodeのフォーマッターがうまく動かず。。。

初めてAWSのLambdaを触ってみた

この記事は、最終目的に向けて複数回にわたってtryする話

最終目的

  • AWSの利用料金を毎朝Slacknに通知して請求額が跳ね上がるのを防ぎたい

なぜそんなことするのか?

  • Terraformの学習をしてた際に、applyした後にdestroyし忘れて個人としては痛い額の請求がきたからです。
    • 個人的観測範囲だと個人でのプロダクト持ってない人では、上位に食い込んでいるのでは?

今回やること

  • 今更だがLambdaに入門してSlackに通知を送ってみること

次回以降に行うこと

  • その日までの利用料金を取得しSlackに通知する
  • deploy方法
    • GithubActions or serverless
    • どちらかを使用して行えるといいなと思っています

参照

qiita.com

前提

  • AWSのアカウントを作成している人
  • Goが動く環境

実装

aws

  • アカウント作成
  • IAMロールの作成

  • aws

    • IAMでユーザーを作成している前提で話を進めます。

      コードを lambda-uploader でデプロイするためには、あらかじめ空の関数を用意する必要があります。

    • と書いてあるので予めにlambdaにて空の関数を作成します
      f:id:hironekosun:20200909160349p:plain
      create_lambda_function
      • 右上に存在している関数の作成を押下、「一から作成」を行います
      • 以下の画像のように入力し、関数の作成を押下します
        f:id:hironekosun:20200909160433p:plain
        create_lambda_function_2

        関数定義の際にポリシーテンプレートから自動作成された IAM ロールに CloudWatchReadOnlyAccess ポリシーをアタッチします。コスト情報を Lambda から読み込むのに必要です。 ロール ARN はあとで必要になるのでメモしておきます。

    • と記載があるのでこちらも対応しましょう
      • IAM > ロールで先ほど作成したロールを選択します。
      • 選択したら以下の画像のように表示されますので
        f:id:hironekosun:20200909160545p:plain
        iam_user_create
      • ポリシーをアタッチしますボタンを押下しましょう
      • 遷移後に検索窓にCloudWatchReadOnlyAccessと入れてチェックを入れて ポリシーのアタッチを行ってください。アタッチが完了したら以下の画像のようになっているはずです。
        f:id:hironekosun:20200909160608p:plain
        policy_attach
  • aws側の作業は一旦ここで終わりです。

Goで作る

package main

import (
    "encoding/json"
    "net/http"
    "net/url"

    "github.com/aws/aws-lambda-go/lambda"
    "github.com/pkg/errors"
)

type Slack struct {
    Blocks []Block `json:"blocks"`
}

type Block struct {
    Type string `json:"type"`
    Text Text `json:"text"`
}

type Text struct {
    Type string `json:"type"`
    Text string `json:"text"`
}

func run() error {
    incomingUrl := "slackのincoming hook URL"

    // slackの指定するjson形式じゃないとエラーになる
    slackMap := Slack{
        Blocks: []Block{
            Block{
                Type: "section",
                Text: Text{
                    Type: "mrkdwn",
                    Text: "検証",
                },
            },
        },
    }

    p, _ := json.Marshal(slackMap)

    resp, err := http.PostForm(
        incomingUrl,
        url.Values{"payload": {string(p)}},
    )

    if err != nil {
        return errors.WithStack(err)
    }

    defer resp.Body.Close()

    return nil

}

func main() {
    lambda.Start(run)
}
  • Goのfileをzipに固める
GOOS=linux GOARCH=amd64 go build -o main
zip handler.zip ./main
  • Go error: \$ GOPATH / go.mod exists but should not
    • このエラーが出たら自分は以下のコマンドで回避しました
export GOPATH=
  • 作成したものをawsのlambdaにアップロードしテストを実行すればslackに通知がきているかと思います

  • 今回は、ここまでとします。

何番煎じかわからないけどmacでphpenvをbuildできるようにする

phpenvのbuild errorが起きすぎる

  • macOSをCatalinaにあげたり、新規macを購入したら大抵エラーが起きたりする
  • 悲しいことに複数のprojectを抱えてたり、version固定(指定)している場合には、phpenvが必須になるので対応するしかない
    • dockerでどうこうの場合は、この限りではないと思います

前提

  • brew が入っている環境
  • anyenvでphpenvがinstall済みの環境

願い・気持ち

  • 極力zshrc等を汚したくない

結論:以下を行えば問題なくbuildができる

  • brew で必要になるものをinstallする
  • anyenv以下にあるphpenvのbuild_optionを修正する
  • phpenv install時にversionによってoptionをつけてコマンドを実行する

  • brew install

$ brew install zlib curl bzip libiconv libedit tidy-html5 openssl@1.1 icu4c krb5

~足りないものは適宜追加してください~

  • build option
$ vim ~/.anyenv/envs/phpenv/plugins/php-build/share/php-build/default_configure_options
--enable-sockets
--enable-exif
--with-zlib
--enable-intl
--with-kerberos
--with-openssl
--enable-soap
--enable-xmlreader
--with-xsl
--enable-ftp
--enable-cgi
--with-curl=/usr
--with-tidy
--with-xmlrpc
--enable-sysvsem
--enable-sysvshm
--enable-shmop
--with-mysqli=mysqlnd
--with-pdo-mysql=mysqlnd
--with-pdo-sqlite
--enable-pcntl
--with-readline
--enable-mbstring
--disable-debug
--enable-fpm
--enable-bcmath
--enable-phpdbg

# 以下を追加
--with-zlib-dir=/usr/local/opt/zlib
--with-bz2=/usr/local/opt/bzip2
--with-iconv=/usr/local/opt/libiconv
--with-libedit=/usr/local/opt/libedit
--with-curl=/usr/local/opt/curl
--with-tidy=/usr/local/opt/tidy-html5
  • php / 7.2.xのinstall
$ PHP_BUILD_CONFIGURE_OPTS="--with-libedit=$(brew --prefix libedit)" phpenv install 7.2.32

7.4.xのinstall

  • zshrcなどに以下を追記
export PKG_CONFIG_PATH="$(brew --prefix krb5)/lib/pkgconfig:$PKG_CONFIG_PATH"
export PKG_CONFIG_PATH="$(brew --prefix openssl@1.1)/lib/pkgconfig:$PKG_CONFIG_PATH"
export PKG_CONFIG_PATH="$(brew --prefix icu4c)/lib/pkgconfig:$PKG_CONFIG_PATH"
$ phpenv install 7.4.x

終わりに

  • qiitaや個人ブログで色々とありますが、みんな苦労しているんだろうなって印象です。
  • 個人的にdockerではなくmacにてphpenvでversion切り替えを行う理由は、mac側からphpコマンドを使いたい場合があるためです。なにかと都合がいいです。