脳汁portal

アメリカ在住(だった)新米エンジニアがその日学んだIT知識を書き綴るブログ

Vagrantで作成したVM環境にパスワード認証でsshログインする

まずはvagrant sshでログインする

# 設定変更
sudo vi /etc/ssh/sshd_config
### 以下のlineのnoをyesに変える ###
PasswordAuthentication no
### ここまで ###

# sshdのrestart
sudo systemctl restart sshd

これでteraterm等からパスワード認証でsshログインできるようになります

ubuntu16.04にrbenvでrubyをインストールする

# library install and update
sudo apt-get update
cd 
sudo apt-get install git build-essential libssl-dev libreadline-dev

vi ~/.profile
### 以下を末尾に追加 ###
export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init -)"
### ここまで ###

source ~/.profile

# installできるversionのチェック
rbenv install --list

# install
rbenv install 2.5.1
rbenv rehash

# globalへの設定&確認
rbenv global 2.5.1
ruby -v

AWS EC2で”JavaScript heap out of memory”エラーが出たら

AWS EC2で”JavaScript heap out of memory”エラーが出たときの対処法です
主にt2.microとかメモリ容量の小さいインスタンスでnuxtとかnode使ってると出現する

こんなエラー

<--- Last few GCs --->

[5746:0x42f2e10]    11947 ms: Mark-sweep 101.0 (114.6) -> 100.2 (113.6) MB, 140.1 / 0.0 ms  (+ 0.3 ms in 1 steps since start of marking, biggest step 0.3 ms, walltime since start of marking 182 ms) (average mu = 0.294, current mu = 0.434) low memory notif[5746:0x42f2e10]    12099 ms: Mark-sweep 100.2 (113.6) -> 100.2 (113.6) MB, 152.0 / 0.0 ms  (average mu = 0.168, current mu = 0.000) low memory notification GC in old space requested

<--- JS stacktrace --->

==== JS stack trace =========================================

Security context: 0x1098ebb9e549 <JSObject>
    0: builtin exit frame: new ArrayBuffer(aka ArrayBuffer)(this=0x1ff267182801 <the_hole>,0x3c9d355d9001 <Number 6.41975e+06>,0x1ff267182801 <the_hole>)

    1: ConstructFrame [pc: 0x3e29d418d145]
    2: StubFrame [pc: 0x3e29d420b900]
    3: createUnsafeArrayBuffer(aka createUnsafeArrayBuffer) [0x3bbd5485d531] [buffer.js:119] [bytecode=0x365108798601 offset=25](this=0x1ff2671826f1 <undefined>,s...

FATAL ERROR: Committing semi space failed. Allocation failed - JavaScript heap out of memory
 1: 0x8c02c0 node::Abort() [node]
 2: 0x8c030c  [node]
 3: 0xad15de v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [node]
 4: 0xad1814 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [node]
 5: 0xebe752  [node]
 6: 0xecb0f2  [node]
 7: 0xecb2b4 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [node]
 8: 0xecc7aa v8::internal::Heap::CollectAllAvailableGarbage(v8::internal::GarbageCollectionReason) [node]
 9: 0xae6cc5 v8::Isolate::LowMemoryNotification() [node]
10: 0x8bfdad node::ArrayBufferAllocator::Allocate(unsigned long) [node]
11: 0xfd88e3 v8::internal::JSArrayBuffer::SetupAllocatingData(v8::internal::Handle<v8::internal::JSArrayBuffer>, v8::internal::Isolate*, unsigned long, bool, v8::internal::SharedFlag) [node]
12: 0xb64514  [node]
13: 0xb664b3 v8::internal::Builtin_ArrayBufferConstructor(int, v8::internal::Object**, v8::internal::Isolate*) [node]
14: 0x3e29d41dc17d

対処法

sudo su
dd if=/dev/zero of=/swapfile bs=1M count=1024
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile

node.jsのインストール方法

# install ndenv
cd /usr/local/
git clone https://github.com/riywo/ndenv.git
chmod -R g+rwxXs /usr/local/ndenv/
mkdir /usr/local/ndenv/plugins
cd /usr/local/ndenv/plugins
git clone https://github.com/riywo/node-build.git
cat << "EOF" > /etc/profile.d/ndenv.sh
export NDENV_ROOT=/usr/local/ndenv
export PATH="$NDENV_ROOT/bin:$PATH"
eval "$(ndenv init -)"
EOF
source /etc/profile.d/ndenv.sh

# install node
ndenv install ${NODE_VERSION}
ndenv global ${NODE_VERSION}
ndenv rehash

Nuxt.jsで画像をwebpで圧縮する

library install

npm install --save gulp-webp

gulpfile.js

vi gulpfile.js

const webp = require('gulp-webp')

gulp.task('webp', function() {
  return gulp.src("./precompile/img/**/*.{svg,gif,png,jpg,jpeg}")
             .pipe(webp())
             .pipe(gulp.dest("./assets/img/"));
});

実行コマンド

gulp webp

流れとしてはprecompile/imgという名前の画像ファイル置き場を作成し、ここにjpgやpngやらの画像を配置する
gulp webpコマンドで上記の画像ファイルをwebpファイルに変換してassets/img以下に配置する
(その後は通常通りnuxt generateでdist以下に画像ファイルが配置される)

AWS lambdaとAPI GatewayとEC2を使って特定のURLにアクセスするだけでインスタンスを作成できるようにする

Lambdaの設定

まずは実際の処理部分をLambdaで設定します

関数の作成

関数の作成を選択して必要な情報を入力していきます。
今回ランタイムはPython2.7を選択しています。
f:id:portaltan:20190322115621p:plain

右下の関数の作成を選択すると以下のような画面が表示されます
f:id:portaltan:20190322115852p:plain

下にゆくと以下のように処理を入力するエリアが出てきます
f:id:portaltan:20190322115948p:plain

import boto3
from botocore.exceptions import ClientError
import json
import logging
import os
import datetime

AMIID=os.environ['AMIID']

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def create_userdata():
    str="""#!/bin/sh
date > /tmp/userdate.log
yum -y update
${PROCESS WHICH YOU WANNA DO}
echo "Finished UserData" > /tmp/userdata.log
"""

    return str

def create_name():
    now = datetime.datetime.now()
    return 'instance_from_lambda_' + now.strftime('%Y/%m/%d %H:%M') + '(UTC)'

def lambda_handler(event, context):
    ret={}

    ec2 = boto3.client('ec2',region_name='ap-northeast-1')
    try:
        res = ec2.run_instances(
                                ImageId=AMIID,
                                InstanceType=event['type'],
                                MinCount=1,
                                MaxCount=1,
                                InstanceInitiatedShutdownBehavior='terminate',
                                IamInstanceProfile={'Name': 'your IAM'},
                                NetworkInterfaces=[{
                                            'DeviceIndex': 0,
                                            'SubnetId' : 'your subnet id',
                                            'Groups': ["your security group"],
                                            'AssociatePublicIpAddress': True            
                                        }],
                                TagSpecifications=[
                                            {
                                                'ResourceType':'instance',
                                                'Tags':[
                                                    {
                                                    'Key':'Name',
                                                    'Value':create_name()
                                                    }
                                                ]
                                            }
                                        ],
                                UserData=create_userdata()
                                )
        
        return res['Instances'][0]['InstanceId']
    except ClientError as e:
        ret['code'] = e.response['Error']['Code']
        ret['message'] = e.response['Error']['Message']
        
        raise json.dumps(ret)    
  • 今回はAMIIDは以下のように環境変数で設定します

f:id:portaltan:20190322133447p:plain

  • instance typeはAPI Gatewayのステージ変数で設定します
  • IAMとsubnet id、security groupは自分の環境に応じたものを入力してください

次にタイムアウトの時間を3秒から15秒に伸ばしておきます
f:id:portaltan:20190322134310p:plain

最後に右上の保存を押して設定を保存します

test

次にまずはLambda単体の設定がうまくいっているかどうかテストをします。
保存の隣にあるテストを選択し、テストに必要なインスタンスタイプを入力します
f:id:portaltan:20190322134519p:plain

次に実際にテストを実行します。うまくいくと次のような表示が現れ、responseとして起動したインスタンスIDが返ってきます。
f:id:portaltan:20190322134625p:plain

以上でLambda側の設定を完了します

API Gatewayの作成

次に上記のLambdaの処理を呼び出すAPIを作成します

リソースの作成

API Gateway > APIの作成を選択し、必要な項目を入力してAPIの作成を選択していきます
f:id:portaltan:20190322114008p:plain

以下のような画面になります
f:id:portaltan:20190322114055p:plain

アクションからリソースの作成を選択し、必要な情報を入力します
f:id:portaltan:20190322114212p:plain

次にステージ変数としてインスタンスタイプを送れるようにします
f:id:portaltan:20190322135343p:plain

次にアクションからメソッドの作成を選択し、ドロップダウンでGETを選択します。
選択すると具体的な設定の入力項目が出現するので、今回は統合タイプは「Lambda関数」、Lambda関数には先ほど作成した関数の「test」を入力します
f:id:portaltan:20190322134752p:plain

次に統合リクエストのマッピングテンプレートから、パラメータの設定を行います。
f:id:portaltan:20190322140803p:plain

{
  "type": "$input.params('type')"
}

APIのデプロイ

作成したAPIはまだデプロイしていないので、明示的にデプロイする必要があります。
以下のようにアクションからAPIのデプロイを選択し、名前をつけてデプロイします

  • 今回はtest_deployとした

f:id:portaltan:20190322134917p:plainf:id:portaltan:20190322134941p:plain

ステージの確認

ステージという項目から、先ほどdeployしたAPIのアクションを確認できます。
GETを選択すると、以下のようにAPIのエンドポイントとパラメーター等を確認できます

Lambdaの確認

先ほどのLambdaの画面に戻ると、トリガーの項目にAPI Gatewayが設定されていることが確認できます。

以上でAPIの設定は完了です

確認

ブラウザから作成したAPIを呼び出してみます
https://${your env}.execute-api.ap-northeast-1.amazonaws.com/test_deploy/create/c5.xlarge

  • 今回はインスタンスタイプとしてc5.xlargeを指定しています
  • ${your env}の部分は自分の環境に応じて上記で確認したエンドポイントを入力してください

Nuxt.js Tips

nuxt.jsのtipsです
f:id:portaltan:20190305115710p:plain

componentに値を渡す場合

送る側
<template>
  <MyComponent />
</template>

<script>
import MyComponent from "~/components/MyComponent.vue"

export default {
  components: {
    MyComponent
  }
}
</scipt>
受け取る側(MyComponet.vue)
<script>
export default {
  props: {
    aaa, bbb, ccc
  }
}
</script>
型チェックをしたい場合
<script>
export default {
  props: {
    aaa: String, 
    bbb: Boolean,
    ccc: Array
  }
}
</script>

propsに直接文字列を入れる場合

<MyComponent :myprop="StringDayo" />

とすると

Raw expression: :myprop="StringDayo"

となる

解決策
<MyComponent myprop="StringDayo" />
or
<MyComponent :myprop="'StringDayo'" />

props等の変数をhtmlの要素にいれたい

<template>
  <nuxt-link to="this.link">
    Link Dayo
  </nuxt-link>
</template>

<script>
export default {
  props: {
    link: String
  }
}
</script>

これだとpropsのlinkが呼ばれずに/this.linkというリンクが参照されてしまう
解決策としては変数を使いたいときには要素の前に:をつける

<nuxt-link :to="this.link">

画像をpropsとして読み込む

<template>
  <div>
    <img :src="image_src" />
  </div>
</template>

<script>
export default {
  props: {
    filename: String,
  },
  data() {
    return {
      image_src: require(`~/assets/img/${this.filename}`)
    }
  }
}
</script>

methodsを追加する場合

<template>
  <div class="foobar" @mouseover="mouseOn()" @mouseout="mouseOut()">
    hogefuga
  </div>
</template>

<script>
export default {
  methods: {
    mouseOn() {
      document.getElementById(foobar).style.color = "red";
    },
    mouseOut() {
      var img = document.getElementById(foobar);
      img.style.color = "gray";
    }
  }
}
</scrip>

ページ読み込み時に処理を行いたいとき

<script>
export default {
  mounted() {
    alert('foobar');
  }
}
</script>

jsonファイルを読み込んで表示する

/static/json/test.json

{
  "aaa": true
  "bbb": false
  "ccc": "hogehoge"
}

/pages/test.vue

<script>
export default {
  data() {
    return {
      json: require(`static/json/test.json`)
    }
  }
}
</script>

▼v-for
要素を繰り返えしたいときに使う
上のjsonファイルを使う

<template>
  <div>
    <ol>
      <li v-for"(value, index) in json" property1="index" property2="value"></li>
    </ol>
  </div>
</template>

<script>
export default {
  data() {
    return {
      json: require(`static/json/test.json`)
    }
  }
}
</script>

v-bind:class

クラス属性を条件分岐で設定するしないを分ける

<template>
  <div>
    <ol>
      <li v-for"(value, index) in json" v-bindclass="{ active: aaa }"></li> // 描画される(aaa === true)
      <li v-for"(value, index) in json" v-bindclass="{ active: bbb }"></li> // 描画されない(bbb === false)
    </ol>
  </div>
</template>

<script>
export default {
  data() {
    return {
      json: require(`static/json/test.json`)
    }
  }
}
</script>

nuxt generateで動的ルーティングもbuild対象にする

nuxtは_foobar.vueといったアンダースコアで始まるvueファイルを作ることで動的にルーティングすることができる。
しかしこの動的ルーティングはデフォルトでnuxt generateの静的ファイルビルドの対象外となっている
以下のようにnuxt.config.jsで明示的に指定してやることで静的なルーティングがビルドされる

module.exports = {
  generate: {
    routes:[
      '/hogehoge',
      '/fuga/fuga'
    ]
  }
}

また、ファイル数が多くなって直接編集が面倒になったときは、以下のようにもできる
staticファイル以下のjsonファイルを読み込んでroutingに設定している

const fs =require('fs')
const filename = fs.readdirSync("./static/").map(x => x.match(/(.*)(?:\.([^.]+$))/)[1])

module.exports = {
  generate: {
    routes: filenames
  }
}

ホットリロードで開発サーバをたてる

npm run dev

listen portを開ける

上記のnpm run devでデバッグサーバをたてることが可能になるが、デフォルトではlocalhostからしかアクセスできない。
なのでnuxt.config.jsで以下のように指定する

module.exports = {
  server: {
    host: '0.0.0.0'
  }
}

rss.xmlを設定する

nuxt generate時にrss.xmlを更新する方法
まずstatic/rss.xmlを作る

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>My Home Page</title>
<link>https://my-home-page.com/</link>
<description>このページはぼくのホームページぃぃぃ!!!</description>
<lastBuildDate>${generate by modules}</lastBuildDate>
<language>ja</language>
<item>
<title>日記</title>
<link>https://my-home-page/blog/</link>
<description>はじめての日記</description>
<pubDate>Fri, 01 Mar 2019 18:00:00 +0900</pubDate>
</item>
</channel>

このstaticファイルの内容はコンパイルされずにそのままdist以下に配置されるので、
nuxt generateが終わった時点で${generate by modules}のところを書き換えてやればよい
modules/hook/generate.js

require('date-utils')
const fs = require ('fs')
const now = new Date()
const rss = fs.readFileSync("static/rss.xml", "utf-8").replace("<lastBuildDate>${generate by modules}</lastBuildDate>", `<lastBuildDate>${now.toFormat('DDD, DD MMM YYYY HH24:MI:SS +0900')}</lastBuildDate>`)

module.exports = function () {
  this.nuxt.hook('generate:done', async generator => {
    fs.writeFileSync("dist/rss.xml", rss);
  })
}

moduleを書いたら、nuxt.config.jsで指定する

module.exports = {
  modules: [
    '@/modules/hook/generate'
  ]
}

bootstrapを読み込む際に二重で読み込まれるのを防ぐ

nuxt.config.js
modules.exports = {
  modules: [
    ['bootstrap-vue/nuxt', { css: false }]
  ]
}

v-if/v-else-if/v-else

コンポーネント等の表示切替をv-if等を使って切り替えることができる

<template>
  <div>
    <img v-if="this.type==='image'" :src="src"></img>
    <video v-else-if="this.tyep==='movie'" :src="src" autoplay loop muted></video>
    <p v-else>missing media</p>
  </div>
</template>

<script>
export default {
  props: {
    type: String,
    link: String
  },
  data() {
    return {
      src: require(`~/assets/img/${this.link}`)
    }
  }
}
</script>

v-for内でv-ifを使う

v-forとv-ifを同じ要素内で使うことは推奨されていない

<template>
  <div>
    <div v-for="(value, index) in objects">
      <div v-if="value">aaa</div>
      <div v-if="index===0">bbb</div>
    </div>
  </div>
</template>

外部リソースを読み込む(bootstrapとかjqueryとか)

<script>
export default {
  head () {
    return {
      script: [
        { src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js' },
        { src: 'https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js' }
      ],
      link: [
        { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css?family=Roboto' }
      ]
    }
  }
}
</script>

custom layoutを読み込む

<script>
export default {
  layout: 'custom'
  }
}
</script>

▼404ページの作成
pages/404/index.vueに作成する

▼動的ルーティングで値を受け取る
pages/_id.vue

<template>
  <div>
    {{parameter}}
  </div>
</template>

<script>
export default {
  asyncData({ params }) {
    return {
      parameter: params.id
    }
  }
}
</script>