Publishing Apps using API Runtime Services 1.6.0

Publish Arrow apps to a newly deployed cluster

Setup

You will need to include Appcelerator CLI 6.2.0+.

  1. Execute this command: sudo npm install appcelerator -g

  2. Update you ~/.bash_profile and source once you modified it with this:

    APPC_DASHBOARD_URL=https://dashboard.cloudapp-1.appctest.com; export APPC_DASHBOARD_URL  

Publish source code

This is the legacy method to publish your arrow app.

  1. Create a new Arrow app:

    appc new -t arrow
    Appcelerator Command-Line Interface, version 6.2.0-master.44
    Copyright (c) 2014-2017, Appcelerator, Inc.  All Rights Reserved.
     
    Preproduction Environment Active
     
    ? What's the project name? appctest
    *** new completed. ***
  2. Update appc.json environment to add extra_hosts if you haven't setup DNS yet:

    Icon

    If your cluster uses appctest.com as a domain name, then you need to add NODE_ENV as well.

    $ cd appctest
    $ vi appc.json
    {
    ...
      "cloud": {
        ...
        // Change your container size here
        "container": "Medium",
        // Number of containers for your app
        "minimum": 3,
        // Maximum number of containers for you app when autoscalling kicks in
        "maximum": 5,
        // NODE_ENV is only needed if
        // a. You are running in an alternately-named environment (like staging)
        // b. You are running on a domain with appctest in the name
        "environment": {"NODE_ENV":"PRODUCTION","extra_hosts":"54.212.208.81 api.cloudapp-1.appctest.com"},
        ...
       }
    }
  3. Prior to Appcelerator CLI 6.2.0, you will need to execute the appc install command.
  4. Execute appc publish to publish the app.

Publish by providing Dockerfile

Starting with API Runtime Services 1.6.0, you can publish to API Runtime Services by pushing a Docker image directly. If your cluster has no Internet access, this is the recommended method to publish.

  1. Create a new Arrow app:

    appc new -t arrow
    Appcelerator Command-Line Interface, version 6.2.0-master.44
    Copyright (c) 2014-2017, Appcelerator, Inc.  All Rights Reserved.
     
    Preproduction Environment Active
     
    ? What's the project name? appctest
    *** new completed. ***
  2. Prepare the Dockerfile by navigating to your project directory and create a Dockerfile and a script file called start_app. This script file is required if you want to be able to retrieve access and console logs of your app when using appc cloud logcat and appc cloud accesslog commands. 

    1. The image must have curl installed in order for arrowcloud to do healthcheck: RUN apk add --no-cache curl

    2. Here are a sample Dockerfile for Arrow apps:

      FROM mhart/alpine-node:5.12.0
      WORKDIR /opt/app
      # Copy files
      COPY . /opt/app
      RUN cd /opt/app
      RUN chmod 700 /opt/app/start_app
      RUN npm install
      ENV NODE_ENV production
      ENTRYPOINT ["/opt/app/start_app"]
    3. Here is the start_app which should be located under the same directory as the Dockerfile: 

      #!/bin/bash
      APP_DIR="/opt/app"
      DEAMON_DIR="/usr/local/bin"
      JQ_EXEC="/usr/bin/jq"
      # command to retrieve the containerId inside docker container
      CONTAINID=$(cat /proc/1/cgroup | grep 'docker/'| tail -1 | sed 's/^.*\///'| cut -c 1-12)
      # replace the fake "serverId" with the real container id
      if[ ! -z $CONTAINID ]; then
          ARROWCLOUD_APP_LOG_DIR=$(echo ${ARROWCLOUD_APP_LOG_DIR} | sed "s/serverId/${CONTAINID}/")
          export serverId=${CONTAINID}
      fi 
      APP_LOG_DIR="/ctdebuglog/${ARROWCLOUD_APP_LOG_DIR}"
      APP_DEBUG_LOG_DIR="${APP_LOG_DIR}/debug"
      APP_REQUESTS_LOG_DIR="${APP_LOG_DIR}/requests"
      mkdir -p "${APP_DEBUG_LOG_DIR}"
      if[ $? -ne 0 ]; then
          echo "Failed to create ${APP_DEBUG_LOG_DIR}"
          exit 1
      fi 
      mkdir -p "${APP_REQUESTS_LOG_DIR}"
      if[ $? -ne 0 ]; then
          echo "Failed to create ${APP_REQUESTS_LOG_DIR}"
          exit 1
      fi
      # make a symbolic link from ${APP_REQUESTS_LOG_DIR} to /ctlog to satisfy appc-logger
      ln -s ${APP_REQUESTS_LOG_DIR} /ctlog
      if[ $? -ne 0 ]; then
          echo "Failed to create link from ${APP_REQUESTS_LOG_DIR} to /ctlog"
          exit 1
      fi 
      STDOUT_LOG_FILE="${APP_DEBUG_LOG_DIR}/stdout.log"
      STDERR_LOG_FILE="${APP_DEBUG_LOG_DIR}/stderr.log"
      functionlogMessage() {
          local message=$1
          echo "[app_launcher] $message">> ${STDOUT_LOG_FILE} 2>>${STDERR_LOG_FILE}
      }
      functionlogError() {
          local errorMessage=$1
          echo "[app_launcher] $errorMessage">> ${STDOUT_LOG_FILE} 2>>${STDERR_LOG_FILE}
      }
      [ -x "$JQ_EXEC"] || { logError "cannot find jq"; exit 1; }
      MAIN_SCRIPT=`cat $APP_DIR/package.json | $JQ_EXEC '.main'| sed -e 's/\"//g'-e 's/^ *//g'`
      START_SCRIPT=`cat $APP_DIR/package.json | $JQ_EXEC '.scripts.start'| sed -e 's/\"//g'-e 's/^ *//g'`
      # main is not empty, will trigger app from assigned main script
      if[ -n "$MAIN_SCRIPT" -a "$MAIN_SCRIPT" != "null" ]; then
          logMessage "main script of package.json: $MAIN_SCRIPT"
          # main is as a file
          if [ -f "$APP_DIR/$MAIN_SCRIPT" ]; then
              DAEMON=node
              DAEMON_ARGS="$DAEMON_ARGS$MAIN_SCRIPT"
          # main is as a folder and there is index.js inside
          elif [ -f "$APP_DIR/$MAIN_SCRIPT/index.js" ]; then
              DAEMON=node
              DAEMON_ARGS="$DAEMON_ARGS$MAIN_SCRIPT/index.js"
          else
              logError "main script ($MAIN_SCRIPT) does not exist and app cannot be started."
              exit 1;
          fi
      # main is not specified, but scripts.start is assigned, will trigger app from scripts.start via npm
      elif [ -n "$START_SCRIPT" -a "$START_SCRIPT" != "null" ]; then
          logMessage "scripts.start of package.json: $START_SCRIPT"
          DAEMON=npm
          DAEMON_ARGS=start
      else
          logError "neither main nor start script is specified and app cannot be started."
          exit 1;
      fi
      # choose Nodejs version
      n $NODE_VERSION
      V1=$(node -v)
      V2=v$NODE_VERSION
      if [ "$V1" != "$V2" ]; then
          logError "Node version $NODE_VERSION is not supported!"
          exit 1
      fi
      cd $APP_DIR
      logMessage "starting application via \"$DAEMON $DAEMON_ARGS\""
      # start the app in the same process which is the main process(pid 1), so that the app can get signals
      exec $DAEMON $DAEMON_ARGS >> ${STDOUT_LOG_FILE} 2>>${STDERR_LOG_FILE}
      RETVAL=$?
      if[ $RETVAL -ne 0 ]; then
          logError "application is over with status code $RETVAL."
      else
          logMessage "application is over."
      fi
      exit $RETVAL
  3. Execute appc publish command to publish the app. If you need to provide the app a version using the --app-version flag, you will need to provide the app name as well since the CLI will not scan the package.json. Executing appc publish will build the Docker image using the provided Dockerfile and push the image directly to API Runtime Services.

    $ cd appctest
    $ appc publish --app-version 1.0.0 appctest

If you need to scale up the number of servers, execute these commands:

  • To set the maximum number of containers allowed for appctest:  appc cloud config appctest --maxsize 5
  • To set the current number of containers for appctest as long as the cluster has enough resources:  appc cloud config appcteest --minsize 2

Publish by using existing Docker image

Alternatively, after preparing your Dockerfile and start_app script as described above, you can build the docker image yourself and publish the image to API Runtime Services. 

  1. Build docker image:

    $ cd appctest
    $ docker build -t appctestimage .
  2. Double check that the image presents locally

    $ docker images |grep appctestimage
    appctestimage               latest              869918dab71b        43 minutes ago      277 MB 
  3. To publish the image directory, make sure you provide the app version (--app-version), image name (--image appctestimage appctest): 

    $ cd appctest
    $ appc publish --app-version 1.0.0 --image appctestimage appctest
  4. If you have not setup you DNS yet, execution of app publish might fail with the following error:

    $ appc cloud logcat appctest
    ...
    Uncaught Exception Error loading connector/appc.arrowdb. Error: getaddrinfo ENOTFOUND api.cloudapp-1.appctest.com api.cloudapp-1.appctest.com:443
    2017-04-03T16:24:39-07:00 | Error: Error loading connector/appc.arrowdb. Error: getaddrinfo ENOTFOUND api.cloudapp-1.appctest.com api.cloudapp-1.appctest.com:443
    ...
  5. In this case, you will need to execute the following command to configure custom host info in the app container. Note: if you try to update /etc/hosts file in Dockerfile with custom hostname and IP, it will not work because the Docker swarm mode will override that info at launch time of the container.

    $ appc cloud config --set "extra_hosts=54.212.208.81 api.cloudapp-1.appctest.com"
    Appcelerator Command-Line Interface, version 6.2.0-master.44
    Copyright (c) 2014-2017, Appcelerator, Inc.  All Rights Reserved.
    Admin Hostname: https://admin.cloudapp-1.appctest.com
    The variable has been saved successfully.
  6. Confirm that your environment is set correctly:

    $ appc cloud config --env appctest
    Appcelerator Command-Line Interface, version 6.2.0-master.44
    Copyright (c) 2014-2017, Appcelerator, Inc.  All Rights Reserved.
    Admin Hostname: https://admin.cloudapp-1.appctest.com
    extra_hosts = 54.212.208.81 api.cloudapp-1.appctest.com

If you need to scale up the number of servers, execute these commands:

  • Set maximum number of containers allowed for appctest: appc cloud con
  • Set current number of containers for appctest as long as the cluster has enough resource): appc cloud config appcteest --minsize 2

Related Links