3

This is a follow-up to my previous question:

I am creating a NodeJS-based image that I install latest Chrome and Chromedriver on, then run a NodeJS-based cron job that uses Selenium Webdriver for testing on a one-minute interval.

This runs in an Azure Container Instance, which is the simplest way to run containers in Azure.

My challenge is that Docker containers in ACI run with 64 MB of dev/shm by default, which causes Chrome failures due to the relatively low amount of memory. Chrome provides a disable-dev-shm-usage flag, but running that creates a memory leak that I can't seem to figure out how to prevent. How can I address this best for my container in ACI, please?

Azure Container Instance Container Memory Consumption
enter image description here

Dockerfile

# 1) Build from this Dockerfile's directory:
#       docker build -t "<some tag>" -f Dockerfile .
# 2) Start the image (e.g. in Docker)
# 3) Observe that the button's value is printed.
# ---------------------------------------------------------------------------------------------

# 1) Use alpine-based NodeJS base image
FROM node:latest

# 2) Install latest stable Chrome
# https://gerg.dev/2021/06/making-chromedriver-and-chrome-versions-match-in-a-docker-image/
RUN echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" | \
    tee -a /etc/apt/sources.list.d/google.list && \
    wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | \
    apt-key add - && \
    apt-get update && \
    apt-get install -y google-chrome-stable libxss1

# 3) Install the Chromedriver version that corresponds to the installed major Chrome version
# https://blogs.sap.com/2020/12/01/ui5-testing-how-to-handle-chromedriver-update-in-docker-image/
RUN google-chrome --version | grep -oE "[0-9]{1,10}.[0-9]{1,10}.[0-9]{1,10}" > /tmp/chromebrowser-main-version.txt
RUN wget --no-verbose -O /tmp/latest_chromedriver_version.txt https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$(cat /tmp/chromebrowser-main-version.txt)
RUN wget --no-verbose -O /tmp/chromedriver_linux64.zip https://chromedriver.storage.googleapis.com/$(cat /tmp/latest_chromedriver_version.txt)/chromedriver_linux64.zip && rm -rf /opt/selenium/chromedriver && unzip /tmp/chromedriver_linux64.zip -d /opt/selenium && rm /tmp/chromedriver_linux64.zip && mv /opt/selenium/chromedriver /opt/selenium/chromedriver-$(cat /tmp/latest_chromedriver_version.txt) && chmod 755 /opt/selenium/chromedriver-$(cat /tmp/latest_chromedriver_version.txt) && ln -fs /opt/selenium/chromedriver-$(cat /tmp/latest_chromedriver_version.txt) /usr/bin/chromedriver

# 4) Set the variable for the container working directory, create and set the working directory
ARG WORK_DIRECTORY=/program
RUN mkdir -p $WORK_DIRECTORY
WORKDIR $WORK_DIRECTORY

# 5) Install npm packages (do this AFTER setting the working directory)
COPY package.json .
RUN npm config set unsafe-perm true
RUN npm i
ENV NODE_ENV=development NODE_PATH=$WORK_DIRECTORY

# 6) Copy script to execute to working directory
COPY runtest.js .

EXPOSE 8080

# 7) Execute the script in NodeJS
CMD ["node", "runtest.js"]

runtest.js

const { Builder, By } = require('selenium-webdriver');
const { Options } = require('selenium-webdriver/chrome');
const cron = require('node-cron');

cron.schedule('*/1 * * * *', async () => await main());

async function main() {
    let driver;

    try {
        //Browser Setup
        let options = new Options()
            .headless()                                 // run headless Chrome
            .excludeSwitches(['enable-logging'])        // disable 'DevTools listening on...'
            .addArguments([
                // no-sandbox is not an advised flag due to security but eliminates "DevToolsActivePort file doesn't exist" error
                'no-sandbox',
                // Docker containers run with 64 MB of dev/shm by default, which causes Chrome failures
                // Disabling dev/shm uses tmp, which solves the problem but appears to result in memory leaks
                'disable-dev-shm-usage'
            ]);
        
        driver = await new Builder().forBrowser('chrome').setChromeOptions(options).build();

        // Navigate to Google and get the "Google Search" button text.
        await driver.get('https://www.google.com');
        let btnText = await driver.findElement(By.name('btnK')).getAttribute('value');
        log(`Google button text: ${btnText}`);
    } catch (e) {
        log(e);
    } finally {
        if (driver) {
            await driver.close();   // helps chromedriver shut down cleanly and delete the "scoped_dir" temp directories that eventually fill up the harddrive.
            await driver.quit();
            driver = null;
            log('   Closed and quit the driver, then set to null.');
        } else {
            log('   *** No driver to close and quit ***');
        }
    }
}

function log(msg) {
    console.log(`${new Date()}: ${msg}`);
}

UPDATE Interestingly, it seems to stabilize once it reaches a certain consumption. The container is allocated 2 GB of memory. I don't see crashes in my app logs, so this seems functional overall.

enter image description here

Simon
  • 1,630
  • 1
  • 17
  • 23

0 Answers0