Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Application Insights support to base images #18

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open

Adding Application Insights support to base images #18

wants to merge 5 commits into from

Conversation

lostintangent
Copy link
Contributor

@lostintangent lostintangent commented Jun 27, 2017

This PR updates the base Node.js images, by adding the Application Insights SDK to them. If the Application Insights integration is enabled for the "hosting" web app (resulting in the ENABLE_APPINSIGHTS env var being set), then the app's startup command will be augmented in order to transparently initialize the Application Insights runtime in-proc. This way, users can enable AI when creating a web app, do a Git push to a default runtime image, and start immediately collecting telemetry, without needing to do anything further. 🚀

If the AI integration feature isn't enabled, then the app will run as usual, with the SDK bits remaining completely inert within the Docker container (similar to the existing default web site bits).

Note: This PR currently only updates the Node 8.1.2 image, in order to get feedback on the implementation. Once the general approach is validated/refined, I'll update the PR to include support for the rest of the default images.

// CC @naziml @nickwalkmsft

@@ -18,15 +18,45 @@ if (typeof process.env.WEBSITE_ROLE_INSTANCE_ID !== 'undefined'
roleInstanceId = process.env.WEBSITE_ROLE_INSTANCE_ID;
}

var startupCommand = fs.readFileSync(CMDFILE, 'utf8').trim();
// Is Application Insights enabled, with an associated ikey?
var appInsightsEnabled = process.env.ENABLE_APPINSIGHTS && process.env.APPINSIGHTS_INSTRUMENTATIONKEY;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per our previous discussion, the expectation is that if the ENABLE_APPINSIGHTS is set, then an Application Insights resource has been provisioned, and its associated instrumentation key has been specified via the APPINSIGHTS_INSTRUMENTATIONKEY variable (which the Application Insights SDK auto-detects).


// The command is using an unknown executable, and therefore,
// the App Insights runtime can't be automatically enabled.
return command;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is primarily meant to support the NPM start script scenario, where the app could specific an arbitrary command, that I'm not aware of a deterministic way to bootstrap Application Insights into. In practice, accommodating PM2 and Node should cover most scenarios.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be fine for now.

// Initialize the Application Insights runtime, using the
// instrumentation key provided by the environment.
require("applicationinsights").setup().start();
} catch (error) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since I check for the presence of the ikey before loading this script, there aren't are known reasons why this would fail. However, I added this error swallowing logic, since I assume we want to be sure this feature add-on doesn't impact the stability of the app itself.

@@ -4,6 +4,7 @@
"description": "Express-driven static site hosted from default App Service wwwroot directory",
"main": "default-static-site.js",
"dependencies": {
"applicationinsights": "0.21.0",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to add the AI SDK dependency here, since the Dockerfile was already running npm install in this directory, and it actually seemed logical to couple any "in-box" dependencies with the startup directory.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, while I used a static version for this dependency, I'm cool with making it pick up patch changes if we thought that made sense. Since these images would need to be rebuilt to pull in any updated dependencies anyways, I figured it made sense to just be explicit about the version being bundled. Would love feedback though.

@@ -47,7 +77,7 @@ if (!startupCommand) {
if (!startupCommand) {
console.log("No startup command or autodetected startup script " +
"found. Running default static site.");
startupCommand = "node " + DEFAULTAPP;
startupCommand = nodeCommandPrefix + DEFAULTAPP;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could potentially skip initializing the AI runtime here, since it's just a "getting started" app. But, if the user enabled AI, they might expect to be able to see the HTTP traffic for the starter, in order to get a feel for the experience, before they push their own app code. Feedback would be appreciated here.

return command;
}

var startupCommand = augmentCommandIfNeccessary(fs.readFileSync(CMDFILE, 'utf8').trim());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides pm2 start <process.json> and node <startScript>, are there any other startup command formats that could be provided by the user and passed in when the Docker container is run? I'm not aware of any, but I just wanted to confirm.

Copy link
Contributor

@nickwalkmsft nickwalkmsft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool stuff. This should be a good enhancement for basic scenarios.

console.log("Found scripts.start in package.json")
startupCommand = 'npm start';
console.log("Found scripts.start in package.json");
startupCommand = augmentCommandIfNeccessary(json.scripts.start.trim());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming there's no great difference between running npm start and extracting the command to run it directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only difference that I can think of is that npm start adds ./node_modules/.bin to the process' PATH, and so it's possible that we could introduce issues for apps that depended on that behavior. However, that's typically only used to be able to run executables in the script that are actually installed as a local Node.js module (e.g. webpack ), so I don't think this should be a problem in practice.

The bigger issue might be the assumption that the start script was running node in the first place, since you could technically put anything else there (e.g. foobar -f server.js).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After thinking about this further, and looking at a set of example apps that have start scripts, I think this logic is sufficient.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better late than never. Another init option for the key is APPLICATIONINSIGHTS_CONNECTION_STRING as seen from the library documentation.

try {
// Initialize the Application Insights runtime, using the
// instrumentation key provided by the environment.
require("applicationinsights").setup().start();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this cause any issues if the user is already doing it in their app?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also - what does this accomplish for your app if you don't have it baked in? I'm assuming it tracks basic metrics?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, doing this init step auto-registers a bunch of metric collection, in order to track telemetry without your app code needing to do anything at all.

If the user was already initializing the AI SDK, then the second call should effectively no-op, but let me verify that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the SDK take any kind of configuration in these calls? If the user is using the SDK, I think their call to it would be the second call (assuming --require would run before anything in the app); if they have done any kind of configuration, that should take precedence.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, the app's call to setup().start() would occur after the bootstrap's script, and therefore, any config that was specified wouldn't take effect unfortunately. That said, the user could provide config via env vars/app settings, which would be automatically picked up/respected by the bootstrap script.

In order to support the scenario where the user chose to auto-enable AI when creating the web app, but also manually added the AI SDK to their app, we may need to make some additional changes to the AI SDK in order to update the config each time the SDK/runtime in initialized.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nickwalkmsft So I confirmed that all AI SDK config can be overridable in app code (e.g. enabling certain types of auto-collected telemetry), except for the actual instrumentation key. However, the app could work around this by simply setting the APPINSIGHTS_INSTRUMENTATIONKEY environment variable (which is actually the recommendation for production anyways), and then everything would work as expected, since the env var would naturally be used by the "first" initialization of the AI SDK.

@nickwalkmsft
Copy link
Contributor

Also, I just had a thought - how does this work on an arbitrary customer app that doesn't use AI? Won't the --require fail since they haven't installed it?

@lostintangent
Copy link
Contributor Author

@nickwalkmsft Since the Docker image itself includes the AI SDK, the --require will always work. The --require arg can reference an arbitrary JS file/module, and that module can in turn reference NPM dependencies which are relative to it, not the app itself. So when we pre-load initAppInsights.js, and that file calls require("applicationinsights"), that applicationinsights module will be resolved from the directory that initiAppInsights.js is within, not the user code directory.

@lostintangent
Copy link
Contributor Author

lostintangent commented Jul 29, 2017

@nickwalkmsft @naziml Let me know if there's anything else you think should be addressed with this PR.

As far as doing some E2E testing, do you think we should just leave this in one Node version and validate the experience a bit further, before replicating it to every Node version? It's already gated behind the ENABLE_APPINSIGHTS env var, but just let me know what you think we should update the PR with support for all Node versions.

@lostintangent
Copy link
Contributor Author

@nickwalkmsft Should we close this PR for now and re-discuss it in the future?

@nickwalkmsft
Copy link
Contributor

Yeah, please go ahead and close. Keep your commits around though; they may be useful later. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants