12
votes

I have a Python script with a Windows .exe dependency, which in return relies on a (closed-source) Windows DLL. The Python script runs just fine in Ubuntu via a call to Wine.

Is it possible (and practical) to run this on AWS Lambda?

What would be involved in preparing the code package?

1
I've got Python Lambdas that have a native library dependency and it works pretty well. Ultimately I package the shared library with in the ZIP and within that zip file, in the "lib" directory, I can put shared libraries. Now that is not a full executable, just .so libraries. You'd have to load Wine as a shared library (no, I'm not sure how) and have it run the .exe. Honestly, you might be better off with a small EC2 running Windows - this sounds like a pain. - stdunbar
This process would be intermittently run, but would still need to run on demand with minimal delay. Lambda seems like the perfect spot for it (other than this closed dependency issue obviously), since otherwise I'd be paying per hour for a machine which may not take any requests for hours at a time. - tremby
Totally agree but, for Lambda at least, you've got a pretty challenging setup. The Windows dependency really makes things difficult. Depending on the load a t2.nano is less than USD $5/month. - stdunbar
So do you imagine that if I wanted to get this working I'd need to compile Wine binaries for Amazon Linux, and package these along with the script and the exe/DLL? Does Lambda have package size limits...? - tremby
To answer my second question there, the uncompressed total package size has to be under 250M, and compressed (zip) it has to be under 50M (source). - tremby

1 Answers

6
votes

Update: the lambda container image feature supports images up to 10gb. I haven't tried it but I think that would be a viable approach, and wouldn't require the hacks I did below to reduce the wine build size.

TL;DR;

Is it Possible? Yes.

Is it practical? The approach I tried is not. A better approach might be to try and put wine into different lambda layers or a custom execution environment.

Will it work for you? It depends, deployment package size and disk space are the limiting factors.


Old, somewhat hacky method to fit wine into the regular lambda environment:

I compiled a custom wine with minimal dependencies for lambda, compressed it and then put it onto S3.

Then, in the lambda at runtime, I downloaded the archive, extracted it to /tmp and ran it with a custom empty wine prefix.

My test windows executable was 64bit curl.exe.

1. Compile Wine for Lambda

From https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html, I first tried amzn-ami-hvm-2018.03.0.20181129-x86_64-gp2, but it had an older compilation environment and wouldn't configure.

With AMI amzn2-ami-hvm-2.0.20190313-x86_64-gp2 on a t3.2xlarge ec2, I was able to configure and compile. These are the commands I used, references aws-compile and building-wine:

> sudo yum groupinstall "Development Tools"
> mkdir -p ~/wine-dirs/wine-source
> git clone git://source.winehq.org/git/wine.git ~/wine-dirs/wine-source
> cd ~/wine-dirs/wine-source
> ./configure --enable-win64 --without-x --without-freetype --prefix /opt/wine
> make -j8
> sudo mkdir -p /opt/wine
> sudo chown ec2-user.ec2-user /opt/wine/
> make install
> cd /opt/
> tar zcvf ~/wine-64.tar.gz wine/

This was only a 64-bit build. It also had almost no other optional wine dependencies.

2. Reduce the size of the Wine build further

I removed a lot of optional dependencies from the wine build at compilation time, but it was still too big. /tmp is limited to 500MB.

I deleted files in the package subdirectories, including what looked like optional libs, until I got it down to around 300MB uncompressed.

I verified that wine would still run curl.exe after deleting files from the build.

3. Compress it

I created a tar.bz2 of wine and curl with default bz2 options, it ended up around 80MB. The compressed and extracted files together required about 390MB.

That way there is enough room to both download the archive and extract it to /tmp inside the lambda.

> du -h .
290M    ./wine/lib64/wine
292M    ./wine/lib64
276K    ./wine/share/wine
8.0K    ./wine/share/applications
288K    ./wine/share
5.0M    ./wine/curl-7.66.0-win64-mingw/bin
5.0M    ./wine/curl-7.66.0-win64-mingw
12M     ./wine/bin
308M    ./wine
390M    .

> ls
wine wine.tar.bz2

4. Upload wine.tar.bz2 to S3

Create an S3 bucket and upload the wine.tar.bz2 file to it.

5. Create the Lambda

Create an AWS Lambda using the python 3.7 runtime. While this uses a different underlying AMI than what wine was built on above, it still worked.

In the lambda execution role, grant access to the S3 bucket.

RAM: 1024MB. I chose this because lambda CPU power scales with the memory.

Timeout: 1 min

6. Lambda code:

I needed to follow the advice from this question and answer to change the wine prefix inside the lambda. I also turned off the display as it suggested.

e.g.:

handler():
  ... download from S3 to /tmp, cd to /tmp

  subprocess.call(["tar", "-jxvf", "./wine.tar.bz2"])
  
  os.environ['DISPLAY'] = ''
  os.environ['WINEARCH'] = 'win64'
  os.environ['WINEPREFIX'] = '/tmp/wineprefix'
  
  subprocess.call(["./wine/bin/wine64", "./wine/curl-7.66.0-win64-mingw/bin/curl.exe", "http://www.stackoverflow.com"])

successful execution of wine in lambda

Success!