aws-cdk
aws-cdk copied to clipboard
[core] allow asset bundling on docker remote host
Core AssetStaging
is now exposing a bundling
option key allowing assets to be built before being uploaded through a docker image.
Unfortunately the docker command is now using volume mount flags to mount input and output folder, making it impossible to execute if docker is set to run on a remote host or in a docker-in-docker env.
Use Case
I encountered this problem trying to build a couple of lambdas (and a custom resource provider) written in typescript on a Gitlab CI instance with docker-in-docker or executing docker commands via docker-machine.
Proposed Solution
My proposal is to create two temporary volumes on target docker env, one for inputs and one for outputs.
Then the first volume can be filled running a busybox
image as helper and calling docker cp
to fill the volume.
Once the cp has finished the helper is stopped and the build command is invoked.
After that, if exit code is 0, another helper is started and the docker cp
is used to copy outputs back to the cdk env.
- [x] :wave: I may be able to implement this feature request
- [ ] :warning: This feature might incur a breaking change
This is a :rocket: Feature Request
I'm also struggling this problem with using aws-lambda-nodejs in a VSCode Remote Container environment.
(/var/run/docker.sock
sharing)
Project workspace is already mounted...
I wish the good old non-docker implementation comes back for compatibility.
Copy @jogold
The plot thickens... (which is a good thing!)
@alekitto thanks for opening this issue.
Can you detail the exact problem you're seeing?
making it impossible to execute if docker is set to run on a remote host or in a docker-in-docker env.
It currently runs without issues in CodeBuild which is a docker-in-docker setup. Can you elaborate on the Gitlab CI setup maybe?
Is your solution something along those lines?
$ docker volume create asset-input
$ docker volume create asset-output
$ docker run -v asset-input:/asset-input -v asset-output:/asset-output --name helper busybox
$ docker cp <asset source> helper:/asset-input
$ docker run --rm -v asset-input:/asset-input -v asset-output/asset-output <user command>
$ docker cp helper:/asset-output <staged bundling dir>
$ docker rm helper
$ docker volume rm asset-input asset-output
For the @aws-lamda-nodejs.NodejsFunction
we currently mount the projet root as the asset source, for a large repo (or even monorepo) this represents lots of files, how does this affect the docker cp
?
Can you detail the exact problem you're seeing?
I'm trying to build compile a lambda from typescript down to js to be executed on node_10.x runtime.
A docker:dind
service container is run and correcly responds to tcp://docker:2375
, then the build starts.
When executing cdk diff, I can see that another node container has been pulled and run by cdk with the arguments set in code bundlingOptions
.
For a coincidence, Gitlab CI mounts the build folder at the same path in all the containers, so the build container and the docker container shares the same cdk code at the same path.
The dependencies are installed correcly, the compilation executes successfully, then an error is thrown stating that Bundling did not produce any output
. Inspecting the docker:dind
container I noticed that the temporary path created by cdk to be mounted as asset-output is created on both containers, but only the one on the docker container is populated with the compiled script.
That's because the -v
docker option mounts maps on the container a volume (or a path) created on the docker host, not the one calling the docker cli.
It currently runs without issues in CodeBuild which is a docker-in-docker setup. Can you elaborate on the Gitlab CI setup maybe?
The build container is a node:14-buster
image with the docker cli added. A service docker:dind
image is run for the job, responding as docker
hostname.
Is your solution something along those lines?
$ docker volume create asset-input $ docker volume create asset-output $ docker run -v asset-input:/asset-input -v asset-output:/asset-output --name helper busybox $ docker cp <asset source> helper:/asset-input $ docker run --rm -v asset-input:/asset-input -v asset-output/asset-output <user command> $ docker cp helper:/asset-output <staged bundling dir> $ docker rm helper $ docker volume rm asset-input asset-output
Yes, with auto-generated id appended to volume names to avoid collisions.
For the
@aws-lamda-nodejs.NodejsFunction
we currently mount the projet root as the asset source, for a large repo (or even monorepo) this represents lots of files, how does this affect thedocker cp
?
IIRC the docker cp
command builds a tar archive internally and streams it to the docker engine, but I don't know if the number of files can affect the performance significantly compared to the size of the files.
Could be related to #8544?
Creating volumes for input and output can avoid osxfs
performance issues on io intensive operations (skipping continuous syncs between macos filesystem and the virtual machine hosting docker).
UPDATE: my current workaround (valid with docker executor on gitlab-runner only) is to configure gitlab-runner to share the /tmp
path mounting it as a volume across all the containers in the same build job.
This way the build (cdk) container and the docker:dind container share the same /tmp
, allowing cdk to find the output files.
UPDATE: my current workaround (valid with docker executor on gitlab-runner only) is to configure gitlab-runner to share the
/tmp
path mounting it as a volume across all the containers in the same build job. This way the build (cdk) container and the docker:dind container share the same/tmp
, allowing cdk to find the output files.
@alekitto This has been fixed in #8601 and released in v1.46.0, can you try with a version >= 1.46.0?
@alekitto This has been fixed in #8601 and released in v1.46.0, can you try with a version >= 1.46.0?
I tried with 1.47 from local, but i cannot make it working when using docker-machine (it throws package.json not exists
error), because the input files are on my computer and it tries to mount a non-existent path on the docker host.
it throws
package.json not exists
error
CDK or inside the container?
it tries to mount a non-existent path
which path?
CDK or inside the container?
The bundling container, when executing npm install
.
which path?
It tries to mount /home/alekitto/my-project-folder/cdk/lib/construct/kms/lambda
which is the path of the code to be compiled on my local machine, currently non-existent in a newly created docker-machine where the docker engine is hosted.
currently non-existent in a newly created docker-machine where the docker engine is hosted.
Not sure I'm following here... you can detail this?
Not sure I'm following here... you can detail this?
I'm not currently executing the docker engine on my local machine.
After docker-machine create --driver amazonec2 docker-aws
a ec2 instance is provisioned to run the docker engine, exposing the docker tcp port (2376).
After provisioning is finished I run eval $(docker-machine env docker-aws)
to be able to run docker command on the newly created host.
Then I try to run cdk diff
which invokes the docker cli (now pointing to the remote docker host).
The command built by cdk is docker run --rm -v /home/alekitto/my-project-folder/cdk/lib/construct/kms/lambda:/asset-input -v /home/alekitto/my-project-folder/cdk/.cdk.staging:/asset-output node:14-buster sh -c "npm install && npm run build && cp app.js /asset-input"
The problem is that /home/alekitto/my-project-folder/cdk
exists on my local machine, but not on the docker host.
When launching that command, the docker host is instructed to mount the specified path on the host machine into the container, but on the host machine that path is non-existent.
The docker engine then creates all the folders, which are obviously empty, and mounts them into the new container.
When the container tries to execute npm install
the command exits with error package.json does not exist
, because the file is not present on the docker host at the specified path.
Inspecting the docker machine via SSH I can see the all the folder structure, but no file is present, because nothing has been copied to the docker host from my local computer.
Was bitten by this issue today when trying to use aws-cdk in a VSCode Remote Container environment in Windows 10. Bundling assets on Docker does not work on Docker in Docker environments running on WSL2.
Edit: Found about ILocalBundling while reading this blog. Worked fine, I guess that's the best solution for people using VS Code + Remote Containers, so any Docker in Docker problem is avoided altogether.
I hit this issue trying to run cdk in docker to with python lambda function: https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_lambda_python/PythonFunction.html
I've posted my toy example here: https://github.com/katrinabrock/aws-cdk-dind-py-lambda
Fails with Bundling did not produce any output. Check that content is written to /asset-output.
I'm not sure where I can see the full docker command that CDK is running to indicate exactly what is being to /asset-output. Traceback references /tmp. I tried mounting /tmp
from my docker host to the container where I'm running CDK as @alekitto suggested, but that didn't solve it.
I am running docker engine locally on OS X.
Full error message
runner_1 | Bundling asset toy-cdk/MyPythonLambda/Code/Stage...
runner_1 | jsii.errors.JavaScriptError:
runner_1 | Error: Bundling did not produce any output. Check that content is written to /asset-output.
runner_1 | at AssetStaging.bundle (/tmp/jsii-kernel-tSWP3N/node_modules/@aws-cdk/core/lib/asset-staging.js:313:19)
runner_1 | at AssetStaging.stageByBundling (/tmp/jsii-kernel-tSWP3N/node_modules/@aws-cdk/core/lib/asset-staging.js:183:14)
runner_1 | at stageThisAsset (/tmp/jsii-kernel-tSWP3N/node_modules/@aws-cdk/core/lib/asset-staging.js:64:41)
runner_1 | at Cache.obtain (/tmp/jsii-kernel-tSWP3N/node_modules/@aws-cdk/core/lib/private/cache.js:28:17)
runner_1 | at new AssetStaging (/tmp/jsii-kernel-tSWP3N/node_modules/@aws-cdk/core/lib/asset-staging.js:88:48)
runner_1 | at new Asset (/tmp/jsii-kernel-tSWP3N/node_modules/@aws-cdk/aws-s3-assets/lib/asset.js:28:25)
runner_1 | at AssetCode.bind (/tmp/jsii-kernel-tSWP3N/node_modules/@aws-cdk/aws-lambda/lib/code.js:225:26)
runner_1 | at new Function (/tmp/jsii-kernel-tSWP3N/node_modules/@aws-cdk/aws-lambda/lib/function.js:95:33)
runner_1 | at new PythonFunction (/tmp/jsii-kernel-tSWP3N/node_modules/@aws-cdk/aws-lambda-python/lib/function.js:34:9)
runner_1 | at /tmp/tmp_fjzvna5/lib/program.js:2700:58
runner_1 |
runner_1 | The above exception was the direct cause of the following exception:
runner_1 |
runner_1 | Traceback (most recent call last):
runner_1 | File "app.py", line 9, in <module>
runner_1 | ToyCdkStack(app, "toy-cdk", env={'region': 'us-west-2'})
runner_1 | File "/usr/local/lib/python3.6/dist-packages/jsii/_runtime.py", line 83, in __call__
runner_1 | inst = super().__call__(*args, **kwargs)
runner_1 | File "/opt/toy-cdk/toy_cdk/toy_cdk_stack.py", line 17, in __init__
runner_1 | entry = '/opt/toy-cdk/lambda/'
runner_1 | File "/usr/local/lib/python3.6/dist-packages/jsii/_runtime.py", line 83, in __call__
runner_1 | inst = super().__call__(*args, **kwargs)
runner_1 | File "/usr/local/lib/python3.6/dist-packages/aws_cdk/aws_lambda_python/__init__.py", line 243, in __init__
runner_1 | jsii.create(PythonFunction, self, [scope, id, props])
runner_1 | File "/usr/local/lib/python3.6/dist-packages/jsii/_kernel/__init__.py", line 272, in create
runner_1 | for iface in getattr(klass, "__jsii_ifaces__", [])
runner_1 | File "/usr/local/lib/python3.6/dist-packages/jsii/_kernel/providers/process.py", line 348, in create
runner_1 | return self._process.send(request, CreateResponse)
runner_1 | File "/usr/local/lib/python3.6/dist-packages/jsii/_kernel/providers/process.py", line 330, in send
runner_1 | raise JSIIError(resp.error) from JavaScriptError(resp.stack)
runner_1 | jsii.errors.JSIIError: Bundling did not produce any output. Check that content is written to /asset-output.
runner_1 | Subprocess exited with error 1
docker_runner_1 exited with code 1
@katrinabrock I have the same exact error that you posted when trying to deploy using this github action: https://github.com/youyo/aws-cdk-github-actions
Were you able to find a fix?
@pkasravi yes!
I solved it by making cdk.out
a mounted volume with the exact same path within the docker container as it is on the host.
https://github.com/katrinabrock/aws-cdk-dind-py-lambda/blob/master/docker-compose.yml#L8
I'm not sure if this is possible with github actions.
@pkasravi I have the same exact error using this github-action. Do you have a workaround?
@AntonUspehov I ended up not using that action and just writing my own
@pkasravi It would be great if you could share the example of your solution or some snippet.
@AntonUspehov sure no problem!
name: CDK Deploy Pipeline
on:
workflow_dispatch:
inputs:
deployEnv:
description: "Deployment Environment (dev/prod)"
required: true
jobs:
Deploy:
runs-on: ubuntu-20.04
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v2
with:
node-version: "14.15.1"
- name: Install CDK
run: |
npm install -g aws-cdk
- name: Use Python 3.8
uses: actions/setup-python@v2
with:
python-version: "3.8"
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Run CDK Deploy
run: |
pip install -r requirements.txt
cdk deploy my-stack
@pkasravi I fail to understand how writing your own action mitigates the Error: Bundling did not produce any output. Check that content is written to /asset-output.
error I am seeing too. Could you elaborate please?
This issue persists. Adding additional info that this fails on circleCI when using:
- setup_remote_docker:
- run:
command: npm cdk synth
Perhaps a naive suggestion, could the bundling output dir be exposed and configured to a public location?
Is the current workaround shown by @pkasravi to stop using docker bundling, and install deps locally?
I tend to use a docker container for my local cdk environment. I encountered this issue running cdk test for a project that includes a aws-s3-deployment.BucketDeployment with a Source.asset with a bundling defined. I resolved it by mounting /tmp as a volume when running my container.
docker run -v /var/run/docker.sock:/var/run/docker.sock -v /home/scarba05/sandbox:/home/scarba05/sandbox -v /tmp:/tmp -it cdk-build /bin/bash
Just ran into this too, specifically with circle's docker in docker setup via setup_remote_docker
, same as the person above
I feel like this means doing docker in docker with CircleCI isn't possible, since (as far as I know) aws cdk isn't providing any kind of hook that I can docker cp to / from.
As mentioned in #21506 I've had similar issues before in other CICD setups. The solution we used there was to use the --volumes-from=$HOSTNAME
as a flag. With that docker will look in the volume of the current running container and should be able to find the path.
I do have prepared a WIP change that I'm not sure if it would be enough to make the parameter accessible at https://github.com/aws/aws-cdk/compare/main...webratz:aws-cdk:master Maybe someone with more insights can have a look if it makes sense to continue there and make it a proper PR. I would probably need a bit help on the tests there, as it needs a specific constellation of docker to make that testable (with the mounted docker sockets as described above)
This issue has received a significant amount of attention so we are automatically upgrading its priority. A member of the community will see the re-prioritization and provide an update on the issue.
Following up with the changes mentioned above: mounting docker volume works, and everything is there in the correct path. This still does not work, as the bundler creates bind mounts, which are always referring to a path on the host, and not on the others containers.
So while with volumesFrom
the actual volume is there, it won't be used as the bind mounts for /asset-input
and /asset-output
don't work.
Looking at previous comments and other issues I think the initial approach that @alekitto suggested, as an alternative to the current bind mount approach would make sense. It could look from a proccess like this:
- Create temporary docker volumes
-
docker volume create asset-input
-
docker volume create asset-output
-
- create helper container & copy files
-
docker run --name copy-helper -v asset-input:/asset-input -v asset-output:/asset-output alpine
-
docker cp foobar/* copy-helper:/asset-input
-
- launch bundling as it is today, but mount the just created volumes instead of bind mount
- copy files via helper container to local host
-
docker cp copy-helper:/asset-output my/cdk.out/.assethash
- clean up
- remove helper container:
docker stop copy-helper
- remove volumes:
docker volume rm -f asset-input
,docker volume rm -f asset-output
- remove helper container:
This should not replace the current variant with bind mounts, but be an optional variant
This is not anywhere close to code that could be published, but its a proof of concept that shows that this approach generally seems to work: https://github.com/aws/aws-cdk/commit/fabddf0c8ea557cad160f9ac41238cf6998357c4
There is a few issues with that like that the docker cp
will always copy the folder into the target, which currently would require an additional mv
to the commands.
Maybe it can be discussed how an actual solution should be structured. Also I'm running out of time to work on this currently, so not sure if and when i could continue
Thanks @webratz
Your POC is very similar to the one I personally use in my projects (obviously I had to copy the dockerExec
function).
Just one thing though: Based on https://docs.docker.com/engine/reference/commandline/cp/ The first copy command should be
dockerExec(['cp', `${this.sourcePath}/.`, `${copyContainerName}:${AssetStaging.BUNDLING_INPUT_DIR}`]);
as if (citing the doc) SRC_PATH
does end with /.
(that is: slash followed by dot) the content of the source directory is copied into this (destination) directory
This would avoid the additional mv
command.
thanks for the hint. i updated my branch. with that it works just fine. now the tricky party will be how to properly integrate and test this in the core lib. i started to dig a little bit into this, but none of the stubs etc can deal with anything else beside a docker run, and actively fail with the other commands that are needed here.